/*
Copyright (c) 2010 Andreas Bjerkeholt
License: MIT
http://www.opensource.org/licenses/mit-license.php
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#if defined(SML)
#include <dingoo/ucos2.h>
#include <dingoo/entry.h>
#include <sml/timer.h>
#else
#include "smltimer.h"
#endif

#include "global.h"
#include "controller.h"
#include "gamefield.h"
#include "graphics.h"
#include "list.h"
#include "crc32.h"
#include "mixer.h"
#include "sound.h"
#include "menu.h"
#include "music.h"

#if !defined(DINGOO_A320) && !defined(GEMEI_760P) && !defined(GEMEI_A330)
#error Missing or invalid platform in makefile.
#endif

#if !defined(SML) && !defined(SDL)
#error Missing or invalid library in makefile.
#endif

#define VERSION_MAJOR    1
#define VERSION_MINOR    0
#define VERSION_REVISION 0

#define DAT_MAGIC_NUMBER 0x0002AAAC


uint8_t     gameMode     = GAME_MODE_SPLASH;
uint8_t     gameModeSub  = GAME_MODE_GAME_SPLASH;

bool exitConfirmDialog = false;

uint8_t configVolume = 15;
uint8_t configSound = CONFIG_SOUND_ON;
uint8_t configMusic = CONFIG_MUSIC_ON;
uint8_t configLastLevel = 1;

level*   cur_level = NULL;
level*   levels;
uint16_t numberOfLevels;

uint16_t gameTimeLeft   = 0;
uint16_t gameLevelNo    = 1;
uint16_t gameChipsLeft  = 0;

char*    gameOverReason = "";

char*    gameOverMessageTime = "Time's up!";
char*    gameOverMessageWater = "One does not simply walk\ninto water without flippers!";
char*    gameOverMessageFire = "The fire here will kill you.\nTry to avoid it, or use fire\nboots.";
char*    gameOverMessageMonster = "Look out for creatures!";
char*    gameOverMessageBlock = "Watch out for moving blocks!";
char*    gameOverMessageBomb = "Booom...";

uint8_t  haveKeysRed;
uint8_t  haveKeysBlue;
uint8_t  haveKeysYellow;
uint8_t  haveKeysGreen;
bool     haveFlippers;
bool     haveFireBoots;
bool     haveIceSkates;
bool     haveSuctionBoots;

uint8_t monsterBaseTile[] = { 0x00, 0x40, 0x44, 0x48, 0x4C, 0x50, 0x54, 0x58, 0x5C, 0x60 };

bool everyOther = true;

char pwText[5] = {0};
uint8_t pwSelected = 0;
uint8_t pwLetterWidth = 8;
uint8_t pwWrong = 0;


void gameScreenshot();
void doLogic();
void doPasswordInput();
void initVictoryAnimation();
void doVictoryAnimation();
void doCloneButtonPressesMonsters();
void doCloneButtonPressesBlocks();
void getDirectionXY(int8_t *dirX, int8_t *dirY, uint8_t direction);
int8_t getDirectionX(uint8_t direction);
int8_t getDirectionY(uint8_t direction);
void doSliding();

void clearMonsters();
int16_t addMonster(uint8_t type, uint8_t x, uint8_t y, uint8_t dir);
int16_t addMonsterEx(uint8_t type, uint8_t x, uint8_t y, uint8_t dir, bool curLevel);
void moveMonsters();
uint8_t getMonsterTypeFromTile(uint8_t tile);
uint8_t getMonsterDirectionFromTile(uint8_t tile);

bool loadGame();
void unloadGame();
bool loadData();
bool saveData();
void startLevel(uint16_t levelNo);
void centerView();
bool movePlayerWithKeys(int8_t newX, int8_t newY);
bool movePlayer(int8_t x, int8_t y, bool tryMove);

bool findNextTeleport(uint8_t objectX, uint8_t objectY, uint16_t teleportIndex, uint8_t direction, uint8_t monsterIndex);
bool isIce(uint8_t tile);
bool isForceFloor(uint8_t tile);
bool isMonsterTrapped(uint8_t monsterIndex);

bool moveMonster(uint8_t monsterIndex, bool tryMove);
bool moveBlock(int8_t oldX, int8_t oldY, int8_t newX, int8_t newY, bool spawning, bool tryMove, bool playerPushed);

uint8_t getDirection(int8_t oldX, int8_t oldY, int8_t newX, int8_t newY);
uint8_t slidingHitWallNewDirection(uint8_t oldTile, uint8_t dir);
uint8_t iceCornerGetNewDirection(uint8_t tile, uint8_t dir);
bool getCloneMachineXY(uint8_t buttonX, uint8_t buttonY, uint8_t *cloneX, uint8_t *cloneY);
void pressCloneButton(uint8_t x, uint8_t y);
void pressTankButton();
bool isTrapOpen(uint16_t index);
void updateTraps();
void toggleAllToggleWalls();
uint32_t modulus(int32_t number, uint32_t modulus);

void slidingProcess();
void slidingInsert(uint8_t direction, uint8_t x, uint8_t y);
void slidingDeleteCur();
bool slidingFind(uint8_t x, uint8_t y);
void clonePressInsert(uint8_t x, uint8_t y);

uint8_t randomDirection();
uint8_t _rand8();
void checkAndFixSpiralsCorruption();
void longTaskBegin();
void longTaskEnd();

timer*   gameTimer    = NULL;
uint32_t gameTickRate = (timer_resolution / 30);
bool     gameRunning  = true;
bool     gameTicksDoNotCatchUp = false;

int8_t   playerX;
int8_t   playerY;
bool     playerSliding;
uint8_t  playerSlideDirection;
bool     playerOnIce;

char*    errorMsg = "Unknown error.";
char*    errorMsg2 = "";

uint8_t  ticksLogic = 0;
uint8_t  timeCycleCount = 0;
uint8_t  extralogicCycleCount = 0; // 0, 1, 0, 1... between every logicCycleCount
bool     movedThisCycle = false;
bool     wasSliding = false;
uint8_t  cyclesSinceNoDpad = 0; // Used to delay movement a bit
uint8_t  inputDelay = 0;
uint8_t  delayAmount = 2;
uint8_t  playerReturnSouthTimer = 10;

monster monsters[128]; // Max 128 monsters in original
cloneCon cloneMachineConnections[31]; // Max 31 in original game
trap traps[19];
trapBut trapButtons[19];

list slidingBlocks;
sliding* slidingCur = NULL;

list cloneButtonPressesMonsters;
list cloneButtonPressesBlocks;

char saveFileName[256];
bool dataShouldSave = false;

char chipsDatFileName[256];

#define VOLUME_INDICATOR_SHOW_TIME 100
uint16_t volumeIndicatorTimeLeft = 0;

#define SELECT_BUTTON_HOLD_TIME 90
uint16_t selectButtonCurHoldTime = 0;
uint16_t screenshotTakenShowTime = 0;
uint16_t splashShowTime = 90;

uint8_t curOption = 0;

rocket rockets[NMBR_OF_ROCKETS];
gfx_color victoryAnimLastUsedColorIndex = 0;
uint8_t victoryFade = 101;

uint32_t creditsAnimCounter;
credit credits[] = {{"Programming", 1, {"Andreas Bjerkeholt (aka Harteex)", "", "", "", ""}},
					{"Graphics", 1, {"Daniel Kvarfordt", "", "", "", ""}},
					{"Music", 1, {"Daniel Kvarfordt", "", "", "", ""}},
					{"Sounds", 1, {"By Brian Raiter (public domain)", "", "", "", ""}},
					{"Special Thanks", 1, {"Ben Brewer (aka Flatmush)", "", "", "", ""}},
					{"Thanks", 5, {"Greg Heier, Anonymous and John Elliott", " - CHIPS.DAT reference", "", "Wikia Chip's Challenge section contributors", " - A lot of valuable gameplay behavior"}},
					{"Thanks", 5, {"John Elion", "- for his Chip's Challenge map editor", "", "Brian Raiter", "- for documentation comparing CC versions"}},
					{"Finally Thanks To", 3, {"Chuck Sommerville", " - for creating the original", "   Chip's Challenge", "", ""}}};
uint8_t creditsSize = 8;

void gameScreenshot() {
	longTaskBegin();

	char tempString[256];
	unsigned long int tempNumber = 0;
	FILE* tempFile;
	while(true) {
		sprintf(tempString, "screenshot%lu.tga", tempNumber);
		tempFile = fopen(tempString, "rb");
		if(tempFile == NULL)
			break;
		fclose(tempFile);
		tempNumber++;
	}
	takeScreenshot(tempString);

	longTaskEnd();
}

// LOGIC AND LOADING

void doLogic()
{
	updateTraps();

	// Check logic timer
	ticksLogic++;

	if (ticksLogic == 3)
	{
		ticksLogic = 0;
		if (inputDelay > 0)
			inputDelay--;

		if (extralogicCycleCount == 0)
		{
			doCloneButtonPressesMonsters();

			everyOther = !everyOther;

			if (wasSliding)
			{
				wasSliding = false;
			}
			else
			{
				if (!playerSliding)
				{
					movedThisCycle = false;
				}
			}

			timeCycleCount++;
			if (timeCycleCount == 5 && gameTimeLeft > 0)
			{
				gameTimeLeft--;
				sprintf(hudTimeLeft, "%i", gameTimeLeft);

				if (gameTimeLeft == 0)
				{
					gameOverReason = gameOverMessageTime;
					gameModeSub = GAME_MODE_GAME_GAMEOVER;
					return;
				}
				else if (gameTimeLeft <= 15)
				{
					playSound(SOUND_TICK);
				}
			}

			// Move monsters
			moveMonsters();

			if (playerReturnSouthTimer == 3)
			{
				if (getTile(playerX, playerY, 2) == TILE_WATER)
					setTile(playerX, playerY, 1, 0x3E);
				else
					setTile(playerX, playerY, 1, 0x6E);
			}

			if (playerReturnSouthTimer < 10)
				playerReturnSouthTimer++;

			if (gameModeSub == GAME_MODE_GAME_GAMEOVER)
				return;

			if (timeCycleCount >= 5)
				timeCycleCount = 0;
		}

		extralogicCycleCount++;
		extralogicCycleCount = extralogicCycleCount % 2;
	}

	// Check controls
	if (!controlPressed(CONTROL_BUTTON4))
	{
		bool newPress = false;
		if (controlJustPressed(CONTROL_DOWN) ||
			controlJustPressed(CONTROL_UP) ||
			controlJustPressed(CONTROL_RIGHT) ||
			controlJustPressed(CONTROL_LEFT))
		{
			inputDelay = 0;
			newPress = true;
		}

		if (movedThisCycle == false && inputDelay == 0 && playerOnIce == false)
		{
			if (controlPressed(CONTROL_DOWN))
			{
				movePlayerWithKeys(playerX, playerY + 1);
			}
			else if (controlPressed(CONTROL_UP))
			{
				movePlayerWithKeys(playerX, playerY - 1);
			}
			else if (controlPressed(CONTROL_RIGHT))
			{
				movePlayerWithKeys(playerX + 1, playerY);
			}
			else if (controlPressed(CONTROL_LEFT))
			{
				movePlayerWithKeys(playerX - 1, playerY);
			}

			if (newPress == true)
				inputDelay = delayAmount;
		}
	}

	// Sliding
	if (ticksLogic == 0)
	{
		doSliding();
	}

	doCloneButtonPressesBlocks();
}

void doPasswordInput()
{
	if (pwWrong > 0)
		pwWrong--;

	uint8_t x = pwSelected % pwLetterWidth;
	uint8_t y = pwSelected / pwLetterWidth;

	if (controlJustPressed(CONTROL_BUTTON1))
	{
		if (pwSelected == 26)
		{
			// Verify password
			if (strcmp(cur_level->password, pwText) == 0)
			{
				gameModeSub = GAME_MODE_GAME_SPLASH;
				configLastLevel = cur_level->number;
			}
			else
			{
				pwText[0] = '\0';
				pwWrong = 100;
			}
		}
		else
		{
			if (strlen(pwText) < 4)
			{
				char c = 'A' + pwSelected;
				strncat(pwText, &c, 1);

				if (strlen(pwText) == 4)
					pwSelected = 26;
			}
		}
	}

	if (controlJustPressed(CONTROL_BUTTON3))
	{
		if (strlen(pwText) > 0)
		{
			pwText[strlen(pwText) - 1] = '\0';
		}
	}

	if (controlJustPressed(CONTROL_BUTTON2))
	{
		exitConfirmDialog = true;
		exitConfirmMenuSelectedItem = 0;
	}

	if (!controlPressed(CONTROL_BUTTON4))
	{
		if (controlJustPressed(CONTROL_LEFT))
		{
			if (y == 3)
			{
				if (pwSelected == 24)
					pwSelected = 26;
				else
					pwSelected--;
			}
			else
			{
				if (x == 0)
					pwSelected += 7;
				else
					pwSelected--;
			}
		}
		else if (controlJustPressed(CONTROL_RIGHT))
		{
			if (y == 3)
			{
				if (pwSelected == 26)
					pwSelected = 24;
				else
					pwSelected++;
			}
			else
			{
				if (x == pwLetterWidth - 1)
					pwSelected -= 7;
				else
					pwSelected++;
			}
		}
		else if (controlJustPressed(CONTROL_UP))
		{
			if (y == 0)
			{
				if (x == 0 || x == 1)
					pwSelected = x + 3 * pwLetterWidth;
				else if (x == 6 || x == 7)
					pwSelected = 26;
				else
					pwSelected = x + 2 * pwLetterWidth;
			}
			else
			{
				if (pwSelected == 26)
					pwSelected = 23;
				else
					pwSelected -= pwLetterWidth;
			}
		}
		else if (controlJustPressed(CONTROL_DOWN))
		{
			if (y == 3)
			{
				if (x == 0 || x == 1)
					pwSelected = x;
				else
					pwSelected = 7;
			}
			else if (y == 2)
			{
				if (x == 0 || x == 1)
					pwSelected += pwLetterWidth;
				else if (x == 6 || x == 7)
					pwSelected = 26;
				else
					pwSelected = x;
			}
			else
			{
				pwSelected += pwLetterWidth;
			}
		}
	}
}

void initVictoryAnimation()
{
	victoryFade = 101;

	uint8_t i, j;
	for (i = 0; i < NMBR_OF_ROCKETS; i++)
	{
		rockets[i].active = false;
		for (j = 0; j < 10; j++)
		{
			rockets[i].sparks[j].ttl = 0;
		}
	}
}

void doVictoryAnimation()
{
	if (victoryFade > 0 && victoryFade <= 100)
	{
		victoryFade -= 5;
	}

	int16_t sparksXs[] = {100, 80, 30, -30, -80, -100, -80, -30, 30, 80};
	int16_t sparksYs[] = {0, 58, 95, 95, 58, 0, -58, -95, -95, -58};
	uint8_t nmbrOfColors = 7;
	gfx_color sparksColorsPri[] = {0x07e0, 0xf800, 0x001f, 0xffe0, 0xf81f, 0xfd40, 0x07ff};
	gfx_color sparksColorsSec[] = {0x0540, 0xa800, 0x0015, 0xad40, 0xa815, 0xcc40, 0x0555};
	
	int8_t gravity = 4;

	uint8_t i, j;
	for (i = 0; i < NMBR_OF_ROCKETS; i++)
	{
		for (j = 0; j < 10; j++)
		{
			if (rockets[i].sparks[j].ttl == 0) // All use the same ttl
				break;

			rockets[i].sparks[j].ttl--;
			rockets[i].sparks[j].ys -= gravity;
			rockets[i].sparks[j].x -= rockets[i].sparks[j].xs;
			rockets[i].sparks[j].y -= rockets[i].sparks[j].ys;
		}

		if (rockets[i].active == false)
		{
			rockets[i].x = rand() % 32000;
			rockets[i].y = 24000;
			rockets[i].xs = ((_rand8() % 20) - 10) * 10;
			rockets[i].ys = ((_rand8() % 10) + 35) * 10;
			rockets[i].ttl = (_rand8() % 40) + 120;
			rockets[i].delay = _rand8() % 90;
			rockets[i].active = true;
			continue;
		}
		
		if (rockets[i].delay > 0)
		{
			rockets[i].delay--;
			continue;
		}

		if (rockets[i].x > 33000)
		{
			rockets[i].active = false;
			continue;
		}

		if (rockets[i].ttl == 0)
		{
			rockets[i].active = false;
			gfx_color tempColorIndex;

			do {
				tempColorIndex = _rand8() % nmbrOfColors;
			} while (victoryAnimLastUsedColorIndex == tempColorIndex);

			victoryAnimLastUsedColorIndex = tempColorIndex;
			
			for (j = 0; j < 10; j++)
			{
				rockets[i].sparks[j].ttl = 80;
				rockets[i].sparks[j].x = rockets[i].x;
				rockets[i].sparks[j].y = rockets[i].y;
				rockets[i].sparks[j].xs = sparksXs[j];
				rockets[i].sparks[j].ys = sparksYs[j];
				rockets[i].sparks[j].colorpri = sparksColorsPri[tempColorIndex];
				rockets[i].sparks[j].colorsec = sparksColorsSec[tempColorIndex];
			}
			continue;
		}

		rockets[i].x -= rockets[i].xs;
		rockets[i].y -= rockets[i].ys;
		rockets[i].ys -= gravity;
		rockets[i].ttl--;
	}
}

void doCloneButtonPressesMonsters()
{
	while (cloneButtonPressesMonsters.count > 0)
	{
		listNode* l = (listNode*)cloneButtonPressesMonsters.first;
		position* p = (position*)l->data;
		pressCloneButton(p->x, p->y);
		listDeleteFirst(&cloneButtonPressesMonsters);
	}
}

void doCloneButtonPressesBlocks()
{
	while (cloneButtonPressesBlocks.count > 0)
	{
		listNode* l = (listNode*)cloneButtonPressesBlocks.first;
		position* p = (position*)l->data;
		pressCloneButton(p->x, p->y);
		listDeleteFirst(&cloneButtonPressesBlocks);
	}
}

/*void doToggleButtonPresses()
{
	while (togglePresses > 0)
	{
		toggleAllToggleWalls();
		togglePresses--;
	}
}*/

void getDirectionXY(int8_t *dirX, int8_t *dirY, uint8_t direction)
{
	switch (direction)
	{
		case DIRECTION_NORTH:
			*dirX = 0;
			*dirY = -1;
			break;
		case DIRECTION_WEST:
			*dirX = -1;
			*dirY = 0;
			break;
		case DIRECTION_SOUTH:
			*dirX = 0;
			*dirY = 1;
			break;
		default:
			*dirX = 1;
			*dirY = 0;
			break;
	}
}

int8_t getDirectionX(uint8_t direction)
{
	switch (direction)
	{
		case DIRECTION_NORTH:
			return 0;
		case DIRECTION_WEST:
			return -1;
		case DIRECTION_SOUTH:
			return 0;
		default:
			return 1;
	}
}

int8_t getDirectionY(uint8_t direction)
{
	switch (direction)
	{
		case DIRECTION_NORTH:
			return -1;
		case DIRECTION_WEST:
			return 0;
		case DIRECTION_SOUTH:
			return 1;
		default:
			return 0;
	}
}

void doSliding()
{
	// Perform player sliding on ice and force floors
	if (playerSliding == true)
	{
		uint8_t i;
		for (i = 0; i < 2; i++)
		{
			movedThisCycle = false;
			int8_t dirX;
			int8_t dirY;
			switch (playerSlideDirection)
			{
				case DIRECTION_NORTH:
					dirX = 0;
					dirY = -1;
					break;
				case DIRECTION_WEST:
					dirX = -1;
					dirY = 0;
					break;
				case DIRECTION_SOUTH:
					dirX = 0;
					dirY = 1;
					break;
				default:
					dirX = 1;
					dirY = 0;
					break;
			}

			bool moved = movePlayer(playerX + dirX, playerY + dirY, false);
			if (moved || !playerOnIce)
			{
				break;
			}
		}

		if (!playerSliding)
		{
			wasSliding = true;
		}
	}

	if (gameModeSub == GAME_MODE_GAME_GAMEOVER)
		return;

	// Perform block sliding
	slidingProcess();

	if (gameModeSub == GAME_MODE_GAME_GAMEOVER)
		return;

	// Monster sliding
	uint8_t i;
	for (i = 0; i < 128; i++)
	{
		if (monsters[i].sliding)
		{
			uint8_t j;
			for (j = 0; j < 2; j++)
			{
				if (moveMonster(i, false))
				{
					break;
				}
			}
		}
	}
}

void clearMonsters()
{
	uint8_t i;
	for (i = 0; i < 128; i++)
	{
		monsters[i].type = MONSTER_NONE;
	}
}

int16_t addMonster(uint8_t type, uint8_t x, uint8_t y, uint8_t dir)
{
	return addMonsterEx(type, x, y, dir, false);
}

// When curLevel is used, it means it's added to the current stored level
// not the actual gamefield you play in.
// When curLevel is false, it refers to the currently loaded playfield,
// and it's then assumed that the monster is added through a clone machine
int16_t addMonsterEx(uint8_t type, uint8_t x, uint8_t y, uint8_t dir, bool curLevel)
{
	monster* p;
	if (curLevel)
		p = cur_level->monsters;
	else
		p = monsters;

	uint8_t i;
	for (i = 0; i < 128; i++)
	{
		if (p[i].type == MONSTER_NONE)
		{
			p[i].type = type;
			p[i].x = x;
			p[i].y = y;
			p[i].sliding = false;
			p[i].moving = MOVING_MOVING;
			p[i].spawning = !curLevel; // if current playfield, set spawning
			p[i].direction = dir;

			return i;
		}
	}

	return -1;
}

void moveMonsters()
{
	uint8_t i;
	uint8_t j;
	int8_t direction;
	for (i = 0; i < 128; i++)
	{
		updateTraps();

		if (monsters[i].sliding)
		{
			continue;
		}

		if (monsters[i].spawning)
		{
			moveMonster(i, false);
			continue;
		}

		if (getTile(monsters[i].x, monsters[i].y, 2) == TILE_CLONE_MACHINE)
		{
			continue;
		}

		direction = getMonsterDirectionFromTile(getTile(monsters[i].x, monsters[i].y, 1));

		if (monsters[i].moving == MOVING_SHOULDMOVE)
		{
			direction = (direction + 1) % 4;
			setTile(monsters[i].x, monsters[i].y, 1, monsterBaseTile[MONSTER_TANK] + direction);
			monsters[i].moving = MOVING_MOVING;
			monsters[i].direction = direction;
		}

		// Trap
		if (isMonsterTrapped(i))
		{
			continue;
		}

		// "Concussion Rule"
		if (getTile(monsters[i].x, monsters[i].y, 2) == TILE_TRAP && (
			monsters[i].type == MONSTER_FIREBALL || monsters[i].type == MONSTER_PINKBALL ||
			monsters[i].type == MONSTER_GLIDER || monsters[i].type == MONSTER_WALKER))
		{
			monsters[i].direction = direction;
			if (moveMonster(i, true))
				moveMonster(i, false);

			continue;
		}

		switch (monsters[i].type)
		{
			case MONSTER_BUG:
				direction++;

				if (direction == 4)
					direction = 0;

				for (j = 0; j < 4; j++)
				{
					monsters[i].direction = modulus(direction - j, 4);
					if (moveMonster(i, false))
						break;
				}

				break;
			case MONSTER_FIREBALL:
				for (j = 0; j < 4; j++)
				{
					int8_t newDir = direction;
					if (j == 1)
						newDir = direction - 1;
					else if (j == 2)
						newDir = direction + 1;
					else if (j == 3)
						newDir = direction + 2;

					monsters[i].direction = modulus(newDir, 4);

					if (moveMonster(i, false))
						break;
				}
				break;
			case MONSTER_PINKBALL:
				monsters[i].direction = direction;
				if (!moveMonster(i, false))
				{
					monsters[i].direction = modulus(direction + 2, 4);
					moveMonster(i, false);
				}
				break;
			case MONSTER_TANK:
				if (monsters[i].moving == MOVING_STOPPED)
				{
					break;
				}

				monsters[i].direction = direction;
				
				if (!moveMonster(i, false))
				{
					monsters[i].moving = MOVING_STOPPED;
				}
				break;
			case MONSTER_GLIDER:
				for (j = 0; j < 4; j++)
				{
					int8_t newDir = direction;
					if (j == 1)
						newDir = direction + 1;
					else if (j == 2)
						newDir = direction - 1;
					else if (j == 3)
						newDir = direction + 2;

					monsters[i].direction = modulus(newDir, 4);

					if (moveMonster(i, false))
						break;
				}
				break;
			case MONSTER_TEETH:
				if (everyOther)
				{
					bool choseVertical = true;
					if (abs(monsters[i].x - playerX) > abs(monsters[i].y - playerY))
					{
						choseVertical = false;
					}

					uint8_t firstDir = 0;
					for (j = 0; j < 2; j++)
					{
						if (choseVertical)
						{
							if (monsters[i].y - playerY > 0)
							{
								if (j == 0)
									firstDir = DIRECTION_NORTH;
								monsters[i].direction = DIRECTION_NORTH;
								if (moveMonster(i, false))
									break;
							}
							else if (monsters[i].y - playerY < 0)
							{
								if (j == 0)
									firstDir = DIRECTION_SOUTH;
								monsters[i].direction = DIRECTION_SOUTH;
								if (moveMonster(i, false))
									break;
							}
						}
						else
						{
							if (monsters[i].x - playerX > 0)
							{
								if (j == 0)
									firstDir = DIRECTION_WEST;
								monsters[i].direction = DIRECTION_WEST;
								if (moveMonster(i, false))
									break;
							}
							else if (monsters[i].x - playerX < 0)
							{
								if (j == 0)
									firstDir = DIRECTION_EAST;
								monsters[i].direction = DIRECTION_EAST;
								if (moveMonster(i, false))
									break;
							}
						}

						choseVertical = !choseVertical;

						if (j == 1) // the monster did not move
						{
							// Set the direction towards the player
							monsters[i].direction = firstDir;
							setTile(monsters[i].x, monsters[i].y, 1, monsterBaseTile[MONSTER_TEETH] + firstDir);
						}
					}
				}
				break;
			case MONSTER_WALKER:
				if (!moveMonster(i, false))
				{
					// First build a set with directions
					uint8_t directions[4] = {DIRECTION_NORTH, DIRECTION_WEST, DIRECTION_SOUTH, DIRECTION_EAST};

					// Then shuffle them 10 times
					uint8_t tempVal;
					uint8_t i1;
					uint8_t i2;
					for (j = 0; j < 10; j++)
					{
						i1 = _rand8() % 4;
						i2 = _rand8() % 4;
						tempVal = directions[i1];
						directions[i1] = directions[i2];
						directions[i2] = tempVal;
					}

					for (j = 0; j < 4; j++)
					{
						monsters[i].direction = directions[j];
						if (moveMonster(i, false))
							break;
					}
				}
				break;
			case MONSTER_BLOB:
				if (everyOther)
				{
					// First build a set with directions
					uint8_t directions[4] = {DIRECTION_NORTH, DIRECTION_WEST, DIRECTION_SOUTH, DIRECTION_EAST};

					// Then shuffle them 10 times
					uint8_t tempVal;
					uint8_t i1;
					uint8_t i2;
					for (j = 0; j < 10; j++)
					{
						i1 = _rand8() % 4;
						i2 = _rand8() % 4;
						tempVal = directions[i1];
						directions[i1] = directions[i2];
						directions[i2] = tempVal;
					}

					for (j = 0; j < 4; j++)
					{
						monsters[i].direction = directions[j];
						if (moveMonster(i, false))
							break;
					}
				}
				break;
			case MONSTER_PARAMECIUM:
				direction--;

				if (direction == -1)
					direction = 3;

				for (j = 0; j < 4; j++)
				{
					monsters[i].direction = modulus(direction + j, 4);
					if (moveMonster(i, false))
						break;
				}
				break;
		}
	}
}

uint8_t getMonsterTypeFromTile(uint8_t tile)
{
	if (tile >= 0x40 && tile <= 0x43)
	{
		return MONSTER_BUG;
	}
	else if (tile >= 0x44 && tile <= 0x47)
	{
		return MONSTER_FIREBALL;
	}
	else if (tile >= 0x48 && tile <= 0x4B)
	{
		return MONSTER_PINKBALL;
	}
	else if (tile >= 0x4C && tile <= 0x4F)
	{
		return MONSTER_TANK;
	}
	else if (tile >= 0x50 && tile <= 0x53)
	{
		return MONSTER_GLIDER;
	}
	else if (tile >= 0x54 && tile <= 0x57)
	{
		return MONSTER_TEETH;
	}
	else if (tile >= 0x58 && tile <= 0x5B)
	{
		return MONSTER_WALKER;
	}
	else if (tile >= 0x5C && tile <= 0x5F)
	{
		return MONSTER_BLOB;
	}
	else if (tile >= 0x60 && tile <= 0x63)
	{
		return MONSTER_PARAMECIUM;
	}
	else
	{
		return MONSTER_NONE;
	}
}

uint8_t getMonsterDirectionFromTile(uint8_t tile)
{
	return tile % 4;
}

void addLineBreaks(char* str)
{
	uint8_t textMaxWidth = 13;

	uint32_t i;
	uint32_t lineStart = 0;
	uint32_t lastSpace = 0;
	for (i = 0; str[i] != NULL; i++)
	{
		if (str[i] == ' ')
		{
			lastSpace = i;
		}

		if (i - lineStart >= textMaxWidth && lastSpace != lineStart)
		{
			str[lastSpace] = '\n';
			lineStart = lastSpace;
		}
	}
}

bool loadGame()
{
	// LOAD GRAPHICS

	if (!loadGfx())
		return false;

	// LOAD LEVELS

	FILE* datFile = fopen(chipsDatFileName, "rb");
	if (datFile == NULL)
	{
		errorMsg = "Unable to open chips.dat";
		errorMsg2 = "You must get chips.dat from Chip's Challenge";
		return false;
	}

	unsigned int crc = getcrc(datFile);
	fseek(datFile, 0, SEEK_SET);

	sprintf(saveFileName, "chips%x.sav", crc);

	uint32_t  _magicNumber;
	uint16_t  _numberOfLevels;

	fread(&_magicNumber, 4, 1, datFile);
	fread(&_numberOfLevels, 2, 1, datFile);

	if (_magicNumber != DAT_MAGIC_NUMBER)
	{
		errorMsg = "Chips.dat is not valid";
		fclose(datFile);
		return false;
	}

	numberOfLevels = _numberOfLevels;

	levels = (level*)malloc(sizeof(level) * numberOfLevels);

	if (levels == NULL)
	{
		errorMsg = "Unable to allocate memory for levels";
		return false;
	}

	uint16_t l;
	for (l = 0; l < numberOfLevels; l++)
	{
		levels[l].teleports = NULL;
	}

	// FOR EACH LEVEL
	for (l = 0; l < numberOfLevels; l++)
	{
		uint16_t  _bytesLevel; // Not including this word
		uint16_t  _levelNo;
		uint16_t  _levelTime;
		uint16_t  _chipsNeeded;
		uint16_t  _mapDetail;
		uint16_t  _bytesLayer1;
		uint8_t*  _tilesDataLayer1;
		uint16_t  _bytesLayer2;
		uint8_t*  _tilesDataLayer2;
		uint16_t  _bytesOptionalFields;
		uint8_t*  _optionalFields;

		fread(&_bytesLevel, 2, 1, datFile);
		fread(&_levelNo, 2, 1, datFile);
		fread(&_levelTime, 2, 1, datFile);
		fread(&_chipsNeeded, 2, 1, datFile);
		fread(&_mapDetail, 2, 1, datFile);
		if (_mapDetail != 1)
		{
			errorMsg = "No map detail in chips.dat";
			fclose(datFile);
			return false;
		}
		fread(&_bytesLayer1, 2, 1, datFile);
		_tilesDataLayer1 = (uint8_t *)malloc(_bytesLayer1 * sizeof(uint8_t));
		if (_tilesDataLayer1 == NULL)
		{
			errorMsg = "Unable to allocate memory (1)";
			fclose(datFile);
			return false;
		}
		fread(_tilesDataLayer1, 1, _bytesLayer1, datFile);
		fread(&_bytesLayer2, 2, 1, datFile);
		_tilesDataLayer2 = (uint8_t *)malloc(_bytesLayer2 * sizeof(uint8_t));
		if (_tilesDataLayer2 == NULL)
		{
			free(_tilesDataLayer1);

			errorMsg = "Unable to allocate memory (2)";
			fclose(datFile);
			return false;
		}
		fread(_tilesDataLayer2, 1, _bytesLayer2, datFile);

		fread(&_bytesOptionalFields, 2, 1, datFile);

		_optionalFields = (uint8_t *)malloc(_bytesOptionalFields * sizeof(uint8_t));
		if (_optionalFields == NULL)
		{
			free(_tilesDataLayer1);
			free(_tilesDataLayer2);

			errorMsg = "Unable to allocate memory (3)";
			return false;
		}
		fread(_optionalFields, 1, _bytesOptionalFields, datFile);

		cur_level = &levels[_levelNo - 1];
		cur_level->number = _levelNo;
		cur_level->timeLimit = _levelTime;
		cur_level->chipsNeeded = _chipsNeeded;
		cur_level->completed = false;
		cur_level->skipped = false;

		// Fill layer 1 & 2
		uint8_t layer;
		for (layer = 1; layer <= 2; layer++)
		{
			uint16_t* _bytesLayer;
			uint8_t*  _tilesDataLayer;
			uint16_t  iSrc;
			uint16_t  iTrg;
			if (layer == 1)
			{
				_bytesLayer = &_bytesLayer1;
				_tilesDataLayer = _tilesDataLayer1;
			}
			else
			{
				_bytesLayer = &_bytesLayer2;
				_tilesDataLayer = _tilesDataLayer2;
			}

			for (iSrc = 0, iTrg = 0; iSrc < *_bytesLayer;)
			{
				uint8_t data = _tilesDataLayer[iSrc];

				if (data == 0xFF)
				{
					uint8_t length = _tilesDataLayer[iSrc+1];
					uint8_t tile = _tilesDataLayer[iSrc+2];
					if (tile >= 0x6C && tile <= 0x6F)
					{
						cur_level->spawn_x = iTrg%LEVELSIZE;
						cur_level->spawn_y = iTrg/LEVELSIZE;
					}
					iSrc += 3;

					uint8_t iTmp;
					for (iTmp = 0; iTmp < length; iTmp++, iTrg++)
					{
						if (layer == 1)
							cur_level->tiles_layer1[iTrg] = tile;
						else
							cur_level->tiles_layer2[iTrg] = tile;
					}
				}
				else
				{
					if (data >= 0x6C && data <= 0x6F)
					{
						cur_level->spawn_x = iTrg%LEVELSIZE;
						cur_level->spawn_y = iTrg/LEVELSIZE;
					}

					if (layer == 1)
						cur_level->tiles_layer1[iTrg] = data;
					else
						cur_level->tiles_layer2[iTrg] = data;

					iTrg++;
					iSrc++;
				}
			}
		}

		// Teleport list
		cur_level->numberOfTeleports = 0;

		uint16_t t;
		for (t = 0; t < (LEVELSIZE * LEVELSIZE); t++)
		{
			if (cur_level->tiles_layer1[t] == TILE_TELEPORT)
				cur_level->numberOfTeleports++;
		}

		if (cur_level->numberOfTeleports > 0)
		{
			cur_level->teleports = (uint16_t*)malloc(cur_level->numberOfTeleports * sizeof(uint16_t));
			if (cur_level->teleports == NULL)
			{
				free(_tilesDataLayer1);
				free(_tilesDataLayer2);
				free(_optionalFields);

				errorMsg = "Unable to allocate memory (4)";
				return false;
			}

			uint16_t j;
			for (t = 0, j = 0; t < (LEVELSIZE * LEVELSIZE); t++)
			{
				if (cur_level->tiles_layer1[t] == TILE_TELEPORT)
				{
					cur_level->teleports[j] = t;
					j++;
				}
			}
		}

		// Default values incase they are missing
		sprintf(cur_level->name, "Untitled");

		uint8_t i;
		for (i = 0; i < 128; i++)
		{
			cur_level->monsters[i].type = MONSTER_NONE;
		}

		uint16_t j;
		uint16_t k;
		uint16_t ci;
		for (j = 0; j < _bytesOptionalFields;)
		{
			uint8_t fieldType = _optionalFields[j];
			uint8_t additionalBytes = _optionalFields[j + 1];
			j += 2;

			switch (fieldType)
			{
				case 0x03: // Map Title
					if (additionalBytes > 64)
					{
						free(_tilesDataLayer1);
						free(_tilesDataLayer2);
						free(_optionalFields);

						errorMsg = "Error in chips.dat.\nLevel names should not exceed 64 bytes.";
						return false;
					}

					memcpy(cur_level->name, &_optionalFields[j], additionalBytes);
					break;
				case 0x04: // Trap Controls
					if (additionalBytes % 10 != 0)
					{
						free(_tilesDataLayer1);
						free(_tilesDataLayer2);
						free(_optionalFields);

						errorMsg = "Error in chips.dat.\nLength of optional field 0x04 must be a multiple of 10.";
						return false;
					}

					for (k = 0, ci = 0; k < additionalBytes; k += 10, ci++)
					{
						cur_level->trapControls[ci].buttonX = _optionalFields[j + k];
						cur_level->trapControls[ci].buttonY = _optionalFields[j + k + 2];
						cur_level->trapControls[ci].trapX = _optionalFields[j + k + 4];
						cur_level->trapControls[ci].trapY = _optionalFields[j + k + 6];
						uint16_t buttonIndex = cur_level->trapControls[ci].buttonX + cur_level->trapControls[ci].buttonY * LEVELSIZE;
						if (buttonIndex >= (LEVELSIZE * LEVELSIZE))
						{
							free(_tilesDataLayer1);
							free(_tilesDataLayer2);
							free(_optionalFields);

							errorMsg = "Error in chips.dat.\nInvalid button position.";
							return false;
						}
						if (cur_level->tiles_layer1[buttonIndex] != TILE_TRAP_BUTTON)
							cur_level->trapControls[ci].open = true;
						else
							cur_level->trapControls[ci].open = false;
					}

					// Set the rest to invalid positions
					while (ci < 19)
					{
						cur_level->trapControls[ci].buttonX = 0xFF;
						cur_level->trapControls[ci].buttonY = 0xFF;
						cur_level->trapControls[ci].trapX = 0xFF;
						cur_level->trapControls[ci].trapY = 0xFF;
						cur_level->trapControls[ci].open = false;
						ci++;
					}
					break;
				case 0x05: // Cloning Machine Controls
					if (additionalBytes % 8 != 0)
					{
						free(_tilesDataLayer1);
						free(_tilesDataLayer2);
						free(_optionalFields);

						errorMsg = "Error in chips.dat.\nLength of optional field 0x05 must be a multiple of 8.";
						return false;
					}

					for (k = 0, ci = 0; k < additionalBytes; k += 8, ci++)
					{
						uint8_t buttonX = _optionalFields[j + k];
						uint8_t buttonY = _optionalFields[j + k + 2];
						uint8_t cloneX = _optionalFields[j + k + 4];
						uint8_t cloneY = _optionalFields[j + k + 6];
						cur_level->cloneConnections[ci].buttonX = buttonX;
						cur_level->cloneConnections[ci].buttonY = buttonY;
						cur_level->cloneConnections[ci].cloneX = cloneX;
						cur_level->cloneConnections[ci].cloneY = cloneY;
					}

					// Set the rest to invalid positions
					while (ci < 31)
					{
						cur_level->cloneConnections[ci].buttonX = 0xFF;
						cur_level->cloneConnections[ci].buttonY = 0xFF;
						cur_level->cloneConnections[ci].cloneX = 0xFF;
						cur_level->cloneConnections[ci].cloneY = 0xFF;
						ci++;
					}
					break;
				case 0x06: // Map Password
					if (additionalBytes > 10)
					{
						free(_tilesDataLayer1);
						free(_tilesDataLayer2);
						free(_optionalFields);

						errorMsg = "Error in chips.dat.\nLevel names should not exceed 64 bytes.";
						return false;
					}

					for (k = 0; k < additionalBytes - 1; k++)
					{
						cur_level->password[k] = _optionalFields[j + k] ^ 0x99;
					}
					cur_level->password[additionalBytes - 1] = '\0';
					break;
				case 0x07: // Map Hint
					memcpy(cur_level->hint, &_optionalFields[j], additionalBytes);
					// Hack for level 16
					if (strstr(cur_level->hint, "Ctrl+R") != NULL)
					{
#if defined(DINGOO_A320) || defined(GEMEI_A330)
						sprintf(cur_level->hint, "Press X to restart any level.");
#elif defined(GEMEI_760P)
						sprintf(cur_level->hint, "Press triangle to restart any level.");
#endif
					}
					addLineBreaks(cur_level->hint);
					break;
				case 0x0A: // Movement
					if (additionalBytes % 2 != 0)
					{
						free(_tilesDataLayer1);
						free(_tilesDataLayer2);
						free(_optionalFields);

						errorMsg = "Error in chips.dat.\nLength of optional field 0x0A must be a multiple of 2.";
						return false;
					}

					for (k = 0; k < additionalBytes; k += 2)
					{
						uint8_t x = _optionalFields[j + k];
						uint8_t y = _optionalFields[j + k + 1];
						uint8_t type = getMonsterTypeFromTile(cur_level->tiles_layer1[y * LEVELSIZE + x]);
						uint8_t dir = getMonsterDirectionFromTile(cur_level->tiles_layer1[y * LEVELSIZE + x]);
						addMonsterEx(type, x, y, dir, true);
					}
					break;
				default:
					// Skip field
					break;
			}

			j += additionalBytes;
		}

		free(_tilesDataLayer1);
		free(_tilesDataLayer2);
		free(_optionalFields);
	}

	// END FOR EACH LEVEL

	fclose(datFile);

	return true;
}

void unloadGame()
{
	uint16_t i;
	for (i = 0; i < numberOfLevels; i++)
	{
		SAFE_FREE(levels[i].teleports);
	}

	SAFE_FREE(levels);

	unloadGfx();

	listDeleteAll(&slidingBlocks);
	listDeleteAll(&cloneButtonPressesMonsters);
	listDeleteAll(&cloneButtonPressesBlocks);
}

bool loadData()
{
	dataShouldSave = true;
	bool error = false;

	FILE* tempFile = fopen(saveFileName, "rb");
	if (tempFile == NULL)
	{
		error = true;
	}
	else
	{
		// Check file
		char id[3];

		fread(&id[0], 1, 1, tempFile);
		fread(&id[1], 1, 1, tempFile);
		fread(&id[2], 1, 1, tempFile);

		if (strncmp(id, "CWD", 3) != 0)
		{
			fclose(tempFile);
			error = true;
		}
	}

	if (error)
	{
		return false;
	}

	uint8_t t;
	uint16_t i;
	for (i = 0; i < numberOfLevels; i++)
	{
		fread(&t, 1, 1, tempFile);
		
		levels[i].completed = (t == 1);
		levels[i].skipped = (t == 2);
	}

	fread(&configVolume, 1, 1, tempFile);
	fread(&configSound, 1, 1, tempFile);
	fread(&configMusic, 1, 1, tempFile);
	fread(&configLastLevel, 1, 1, tempFile);

	if (configVolume > VOLUME_MAX)
		configVolume = VOLUME_MAX;

	if (configSound > CONFIG_SOUND_OFF)
		configSound = CONFIG_SOUND_OFF;

	if (configMusic > CONFIG_MUSIC_OFF)
		configMusic = CONFIG_MUSIC_OFF;

	if (configLastLevel == 0)
		configLastLevel = 1;

	if (configLastLevel > 149)
		configLastLevel = 149;

	fclose(tempFile);
	return true;
}

bool saveData()
{
	FILE* tempFile = fopen(saveFileName, "wb");
	if (tempFile == NULL)
		return false;

	char* id = "CWD";
	fwrite(&id[0], 1, 1, tempFile);
	fwrite(&id[1], 1, 1, tempFile);
	fwrite(&id[2], 1, 1, tempFile);

	uint8_t zero = 0;
	uint8_t one = 1;
	uint8_t two = 2;
	uint16_t i;
	for (i = 0; i < numberOfLevels; i++)
	{
		if (levels[i].completed)
			fwrite(&one, 1, 1, tempFile);
		else if (levels[i].skipped)
			fwrite(&two, 1, 1, tempFile);
		else
			fwrite(&zero, 1, 1, tempFile);
	}
	uint8_t vol = (uint8_t)mixerVolume;
	fwrite(&vol, 1, 1, tempFile);
	fwrite(&configSound, 1, 1, tempFile);
	fwrite(&configMusic, 1, 1, tempFile);
	fwrite(&configLastLevel, 1, 1, tempFile);

	fclose(tempFile);
	return true;
}

uint16_t numberOfSkippedLevels()
{
	uint16_t i;
	uint16_t skipped = 0;
	for (i = 0; i < numberOfLevels; i++)
	{
		if (levels[i].skipped)
			skipped++;
	}

	return skipped;
}

bool isLevelSkippable(uint16_t levelNo)
{
	if (levelNo > numberOfLevels)
		return false;
	if (levelNo == 0)
		return false;
	if (levelNo == 144 || levelNo == 149)
		return false;
	if (levels[levelNo - 1].completed || levels[levelNo - 1].skipped)
		return false;
	if (levelNo > 1)
	{
		if (!(levels[levelNo - 2].completed || levels[levelNo - 2].skipped))
			return false;
	}

	uint16_t skipped = numberOfSkippedLevels();

	if (skipped < SKIPPABLE_LEVELS)
	{
		return true;
	}
	else
	{
		return false;
	}
}

bool isCurLevelSkippable()
{
	return isLevelSkippable(cur_level->number);
}

void skipLevel(uint16_t levelNo)
{
	if (isLevelSkippable(levelNo))
	{
		levels[levelNo - 1].skipped = true;
		startLevel(levelNo + 1);
	}
}

void startLevel(uint16_t levelNo)
{
	if (levelNo > numberOfLevels)
	{
		gameMode = GAME_MODE_VICTORY;
		initVictoryAnimation();
		return;
	}

	bool showPw = false;
	if (levelNo > 1 && !((levels[levelNo - 1].completed || levels[levelNo - 1].skipped) || (levels[levelNo - 2].completed || levels[levelNo - 2].skipped)))
	{
		if (configLastLevel != levelNo)
		{
			if (cur_level == NULL)
				showPw = true;
			else if (cur_level->number != levelNo)
				showPw = true;
			else if (cur_level->number == levelNo && gameModeSub == GAME_MODE_GAME_PASSWORD)
				showPw = true;
		}
	}

	if (!showPw)
		configLastLevel = levelNo;

	cur_level = &levels[levelNo - 1];

	timeCycleCount = 0;

	// Copy the current level (since we modify it while we play it)
	uint16_t i;
	for (i = 0; i < (LEVELSIZE * LEVELSIZE); i++)
	{
		tilesLayer1[i] = cur_level->tiles_layer1[i];
		tilesLayer2[i] = cur_level->tiles_layer2[i];
	}

	playerX = cur_level->spawn_x;
	playerY = cur_level->spawn_y;
	playerSliding = false;
	wasSliding = false;
	playerOnIce = false;

	listDeleteAll(&slidingBlocks);
	listDeleteAll(&cloneButtonPressesMonsters);
	listDeleteAll(&cloneButtonPressesBlocks);

	for (i = 0; i < 128; i++)
	{
		monsters[i].type = cur_level->monsters[i].type;
		monsters[i].x = cur_level->monsters[i].x;
		monsters[i].y = cur_level->monsters[i].y;
		monsters[i].sliding = false;
		monsters[i].moving = MOVING_MOVING;
		monsters[i].spawning = false;
		monsters[i].direction = cur_level->monsters[i].direction;
	}

	for (i = 0; i < 31; i++)
	{
		cloneMachineConnections[i].buttonX = cur_level->cloneConnections[i].buttonX;
		cloneMachineConnections[i].buttonY = cur_level->cloneConnections[i].buttonY;
		cloneMachineConnections[i].cloneX = cur_level->cloneConnections[i].cloneX;
		cloneMachineConnections[i].cloneY = cur_level->cloneConnections[i].cloneY;
	}

	for (i = 0; i < 19; i++)
	{
		traps[i].trapX = 0xFF;
		traps[i].trapY = 0xFF;
		traps[i].open = false;
		trapButtons[i].buttonX = 0xFF;
		trapButtons[i].buttonY = 0xFF;
	}

	for (i = 0; i < 19; i++)
	{
		// Check if the trap is already in list (multiple buttons may control one trap)
		// If it's not found, j is the first unused index
		uint8_t j;
		for (j = 0; j < 19; j++)
		{
			if (traps[j].trapX == 0xFF)
			{
				traps[j].trapX = cur_level->trapControls[i].trapX;
				traps[j].trapY = cur_level->trapControls[i].trapY;
				break;
			}
			else if (traps[j].trapX == cur_level->trapControls[i].trapX &&
				traps[j].trapY == cur_level->trapControls[i].trapY)
			{
				break;
			}
		}

		trapButtons[i].buttonX = cur_level->trapControls[i].buttonX;
		trapButtons[i].buttonY = cur_level->trapControls[i].buttonY;
		trapButtons[i].trapIndex = j;
	}

	centerView();

	gameLevelNo = cur_level->number;
	gameTimeLeft = cur_level->timeLimit;
	gameChipsLeft = cur_level->chipsNeeded;

	haveKeysRed = 0;
	haveKeysBlue = 0;
	haveKeysYellow = 0;
	haveKeysGreen = 0;
	haveFlippers = false;
	haveFireBoots = false;
	haveIceSkates = false;
	haveSuctionBoots = false;

	sprintf(hudLevelNo, "%i", gameLevelNo);
	sprintf(hudChipsLeft, "%i", gameChipsLeft);
	if (gameTimeLeft > 0)
		sprintf(hudTimeLeft, "%i", gameTimeLeft);
	else
		sprintf(hudTimeLeft, "--");

	sprintf(hudPasswordStr, "PASSWORD: %s", cur_level->password);

	gameMode = GAME_MODE_GAME;
	gameModeSub = GAME_MODE_GAME_SPLASH;
	movedThisCycle = false;

	if (showPw)
	{
		pwSelected = 0;
		pwText[0] = '\0';
		pwWrong = 0;

		gameModeSub = GAME_MODE_GAME_PASSWORD;
	}
}

void centerView()
{
	tileOffsetX = ((tilesVisibleWidth - 1) / 2) - playerX;
	tileOffsetY = ((tilesVisibleHeight - 1) / 2) - playerY;

	if (tileOffsetX > 0)
		tileOffsetX = 0;

	if (tileOffsetX < -(LEVELSIZE - tilesVisibleWidth))
		tileOffsetX = -(LEVELSIZE - tilesVisibleWidth);

	if (tileOffsetY > 0)
		tileOffsetY = 0;

	if (tileOffsetY < -(LEVELSIZE - tilesVisibleHeight))
		tileOffsetY = -(LEVELSIZE - tilesVisibleHeight);
}

bool movePlayerWithKeys(int8_t newX, int8_t newY)
{
	if (movedThisCycle == true)
		return false;

	movedThisCycle = true;

	if (!haveIceSkates && (getTile(playerX, playerY, 2) == TILE_ICE ||
		getTile(playerX, playerY, 2) == TILE_ICE_CORNER_SE || getTile(playerX, playerY, 2) == TILE_ICE_CORNER_SW ||
		getTile(playerX, playerY, 2) == TILE_ICE_CORNER_NW || getTile(playerX, playerY, 2) == TILE_ICE_CORNER_NE))
		return false;

	if (playerSliding && getDirection(playerX, playerY, newX, newY) == playerSlideDirection)
		return false;

	return movePlayer(newX, newY, false);
}

bool movePlayer(int8_t newX, int8_t newY, bool tryMove)
{
	bool hitWall = false;

	uint8_t dir = getDirection(playerX, playerY, newX, newY);

	uint16_t newIndex = newX + newY * LEVELSIZE;
	uint16_t oldIndex = playerX + playerY * LEVELSIZE;
	uint8_t oldTile = tilesLayer2[oldIndex];
	uint8_t newTile = TILE_WALL;

	if (newX < 0 || newY < 0 || newX >= LEVELSIZE || newY >= LEVELSIZE)
	{
		hitWall = true;
	}
	else if (tilesLayer2[newIndex] == TILE_CLONE_MACHINE)
	{
		hitWall = true;
	}
	else
	{
		newTile = tilesLayer1[newIndex];

		if ((oldTile == TILE_BLOCKED_N && dir == DIRECTION_NORTH) ||
			(oldTile == TILE_BLOCKED_W && dir == DIRECTION_WEST) ||
			(oldTile == TILE_BLOCKED_S && dir == DIRECTION_SOUTH) ||
			(oldTile == TILE_BLOCKED_E && dir == DIRECTION_EAST) ||
			(oldTile == TILE_BLOCKED_SE && (dir == DIRECTION_SOUTH || dir == DIRECTION_EAST)))
		{
			hitWall = true;
		}
		else if (oldTile == TILE_TRAP && !isTrapOpen(oldIndex))
		{
			hitWall = true;
		}
		else if (newTile == TILE_MOVABLE_BLOCK)
		{
			if (!moveBlock(newX, newY, newX + newX - playerX, newY + newY - playerY, false, false, true))
			{
				hitWall = true;
			}
			else
			{
				updateTraps();
			}
		}

		// Recheck new tile in case it was movable block
		newTile = tilesLayer1[newIndex];

		if (oldTile == TILE_CLONE_MACHINE)
		{
			hitWall = true;
		}

		if (newTile >= 0x40 && newTile <= 0x6B)
			newTile = tilesLayer2[newIndex];

		// Check if player hit wall
		switch (newTile)
		{
			case TILE_FLOOR:
			case TILE_CHIP:
			case TILE_WATER:
			case TILE_FIRE:
				break;
			case TILE_BLOCKED_N:
				if (dir == DIRECTION_SOUTH)
					hitWall = true;
				break;
			case TILE_BLOCKED_W:
				if (dir == DIRECTION_EAST)
					hitWall = true;
				break;
			case TILE_BLOCKED_S:
				if (dir == DIRECTION_NORTH)
					hitWall = true;
				break;
			case TILE_BLOCKED_E:
				if (dir == DIRECTION_WEST)
					hitWall = true;
				break;
			case TILE_DIRT:
			case TILE_ICE:
			case TILE_FORCE_S:
			case TILE_FORCE_N:
			case TILE_FORCE_E:
			case TILE_FORCE_W:
			case TILE_EXIT:
				break;
			case TILE_BLUE_DOOR:
				if (haveKeysBlue == 0)
					hitWall = true;
				break;
			case TILE_RED_DOOR:
				if (haveKeysRed == 0)
					hitWall = true;
				break;
			case TILE_GREEN_DOOR:
				if (haveKeysGreen == 0)
					hitWall = true;
				break;
			case TILE_YELLOW_DOOR:
				if (haveKeysYellow == 0)
					hitWall = true;
				break;
			case TILE_ICE_CORNER_SE:
				if (dir == DIRECTION_SOUTH || dir == DIRECTION_EAST)
					hitWall = true;
				break;
			case TILE_ICE_CORNER_SW:
				if (dir == DIRECTION_SOUTH || dir == DIRECTION_WEST)
					hitWall = true;
				break;
			case TILE_ICE_CORNER_NW:
				if (dir == DIRECTION_NORTH || dir == DIRECTION_WEST)
					hitWall = true;
				break;
			case TILE_ICE_CORNER_NE:
				if (dir == DIRECTION_NORTH || dir == DIRECTION_EAST)
					hitWall = true;
				break;
			case TILE_BLUE_BLOCK_F:
			case TILE_THIEF:
				break;
			case TILE_SOCKET:
				if (gameChipsLeft > 0)
					hitWall = true;
				break;
			case TILE_TOGGLE_BUTTON:
			case TILE_CLONE_BUTTON:
			case TILE_TOGGLE_OPEN:
			case TILE_TRAP_BUTTON:
			case TILE_TANK_BUTTON:
			case TILE_TELEPORT:
			case TILE_BOMB:
			case TILE_TRAP:
			case TILE_GRAVEL:
			case TILE_RECESSED_WALL:
			case TILE_HINT:
				break;
			case TILE_BLOCKED_SE:
				if (dir == DIRECTION_NORTH || dir == DIRECTION_WEST)
					hitWall = true;
				break;
			case TILE_FORCE_RANDOM:
				break;
			default:
				if (newTile < 0x40 || newTile > 0x6B) // Finally filter out monsters, keys and boots
					hitWall = true;
		}
	}

	if (tryMove)
	{
		return !hitWall;
	}

	playerReturnSouthTimer = 0;

	if (hitWall)
	{
		if (getTile(playerX, playerY, 2) == TILE_FORCE_RANDOM)
		{
			playerSlideDirection = randomDirection();
		}

		// Set visible direction
		if (tilesLayer2[oldIndex] == TILE_WATER)
			tilesLayer1[oldIndex] = 0x3C + dir;
		else
			tilesLayer1[oldIndex] = 0x6C + dir;

		if (newTile == TILE_BLUE_BLOCK_R || newTile == TILE_HIDDEN_WALL)
		{
			tilesLayer1[newIndex] = TILE_WALL;
		}

		playSound(SOUND_HITWALL);

		if (playerSliding)
		{
			// Change direction
			if ((oldTile >= 0x1A && oldTile <= 0x1D) || oldTile == TILE_ICE || oldTile == TILE_TELEPORT)
			{
				uint8_t newDir = slidingHitWallNewDirection(oldTile, dir);
				playerSlideDirection = newDir;
				return false;
			}
			
			return false;
		}

		return false;
	}

	newTile = tilesLayer1[newIndex];

	switch (newTile)
	{
		case TILE_FLOOR:
			break;
		case TILE_CHIP:
			if (gameChipsLeft > 0)
			{
				gameChipsLeft--;
				sprintf(hudChipsLeft, "%d", gameChipsLeft);
			}
			playSound(SOUND_CHIP);
			break;
		case TILE_WATER:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			if (!haveFlippers)
			{
				gameOverReason = gameOverMessageWater;
				gameModeSub = GAME_MODE_GAME_GAMEOVER;
			}
			break;
		case TILE_FIRE:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			if (!haveFireBoots)
			{
				gameOverReason = gameOverMessageFire;
				gameModeSub = GAME_MODE_GAME_GAMEOVER;
			}
			break;
		case TILE_MOVABLE_BLOCK:
			// Handled elsewhere
			break;
		case TILE_DIRT:
			break;
		case TILE_ICE:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			if (!haveIceSkates)
			{
				playerSliding = true;
				playerOnIce = true;

				playerSlideDirection = getDirection(playerX, playerY, newX, newY);
			}
			break;
		case TILE_FORCE_S:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			if (!haveSuctionBoots)
			{
				playerSliding = true;
				playerSlideDirection = DIRECTION_SOUTH;
			}
			break;
		case TILE_FORCE_N:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			if (!haveSuctionBoots)
			{
				playerSliding = true;
				playerSlideDirection = DIRECTION_NORTH;
			}
			break;
		case TILE_FORCE_E:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			if (!haveSuctionBoots)
			{
				playerSliding = true;
				playerSlideDirection = DIRECTION_EAST;
			}
			break;
		case TILE_FORCE_W:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			if (!haveSuctionBoots)
			{
				playerSliding = true;
				playerSlideDirection = DIRECTION_WEST;
			}
			break;
		case TILE_EXIT:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			gameModeSub = GAME_MODE_GAME_WON;
			cur_level->completed = true;
			cur_level->skipped = false;
			playSound(SOUND_WIN);
			break;
		case TILE_BLUE_DOOR:
			playSound(SOUND_DOOR);
			haveKeysBlue--;
			break;
		case TILE_RED_DOOR:
			playSound(SOUND_DOOR);
			haveKeysRed--;
			break;
		case TILE_GREEN_DOOR:
			playSound(SOUND_DOOR);
			break;
		case TILE_YELLOW_DOOR:
			playSound(SOUND_DOOR);
			haveKeysYellow--;
			break;
		case TILE_ICE_CORNER_SE:
		case TILE_ICE_CORNER_SW:
		case TILE_ICE_CORNER_NW:
		case TILE_ICE_CORNER_NE:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			if (!haveIceSkates)
			{
				uint8_t newDir = iceCornerGetNewDirection(newTile, dir);
				
				playerSlideDirection = newDir;
				playerSliding = true;
			}
			break;
		case TILE_BLUE_BLOCK_F:
			break;
		case TILE_THIEF:
			playSound(SOUND_THIEF);

			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			haveFlippers = false;
			haveFireBoots = false;
			haveIceSkates = false;
			haveSuctionBoots = false;
			break;
		case TILE_SOCKET:
			playSound(SOUND_SOCKET);
			break;
		case TILE_TOGGLE_BUTTON:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			toggleAllToggleWalls();
			break;
		case TILE_CLONE_BUTTON:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			clonePressInsert(newX, newY);
			break;
		case TILE_TRAP_BUTTON:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			playSound(SOUND_BUTTON);
			break;
		case TILE_TANK_BUTTON:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			pressTankButton();
			break;
		case TILE_TELEPORT:
			// We'll handle this partly like sliding on ice, since it's pretty much its behaviour
			playerSliding = true;
			playerOnIce = true;
			playerSlideDirection = getDirection(playerX, playerY, newX, newY);

			if (findNextTeleport(playerX, playerY, newIndex, playerSlideDirection, 0))
			{
				if (!(newX == playerX && newY == playerY))
					playSound(SOUND_TELEPORT);

				tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
				tilesLayer2[oldIndex] = 0x00;
				return true;
			}

			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			break;
		case TILE_BOMB:
			gameOverReason = gameOverMessageBomb;
			gameModeSub = GAME_MODE_GAME_GAMEOVER;
			break;
		case TILE_RECESSED_WALL:
			tilesLayer2[newIndex] = TILE_WALL;
			break;
		case TILE_HINT:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			break;
		case TILE_FORCE_RANDOM:
			tilesLayer2[newIndex] = tilesLayer1[newIndex];
			if (!haveSuctionBoots)
			{
				playerSliding = true;
				playerSlideDirection = randomDirection();
			}
			break;
		case 0x64:
			playSound(SOUND_ITEM);
			haveKeysBlue++;
			break;
		case 0x65:
			playSound(SOUND_ITEM);
			haveKeysRed++;
			break;
		case 0x66:
			playSound(SOUND_ITEM);
			haveKeysGreen = 1; // Green are always infinite
			break;
		case 0x67:
			playSound(SOUND_ITEM);
			haveKeysYellow++;
			break;
		case 0x68:
			playSound(SOUND_ITEM);
			haveFlippers = true;
			break;
		case 0x69:
			playSound(SOUND_ITEM);
			haveFireBoots = true;
			break;
		case 0x6A:
			playSound(SOUND_ITEM);
			haveIceSkates = true;
			break;
		case 0x6B:
			playSound(SOUND_ITEM);
			haveSuctionBoots = true;
			break;
		default:
			if (newTile >= 0x40 && newTile <= 0x63) // Monsters, too much to have as cases in the switch
			{
				tilesLayer2[newIndex] = tilesLayer1[newIndex];
				gameOverReason = gameOverMessageMonster;
				gameModeSub = GAME_MODE_GAME_GAMEOVER;
				break;
			}
			else
			{
				tilesLayer2[newIndex] = tilesLayer1[newIndex];
				break;
			}
	}

	// If the destination is neither ice nor force floor nor teleport
	if ((!isIce(tilesLayer1[newIndex]) || haveIceSkates) && tilesLayer1[newIndex] != TILE_TELEPORT)
	{
		playerOnIce = false;

		if (!isForceFloor(tilesLayer1[newIndex]) || haveSuctionBoots)
		{
			playerSliding = false;
		}
	}

	tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
	tilesLayer2[oldIndex] = 0x00;

	updateTraps();

	if (tilesLayer1[newIndex] == TILE_WATER)
	{
		if (haveFlippers)
		{
			tilesLayer1[newIndex] = 0x3C + dir;
		}
		else
		{
			tilesLayer1[newIndex] = TILE_DROWNING_CHIP;
		}
	}
	else if (tilesLayer1[newIndex] == TILE_FIRE && !haveFireBoots)
	{
		tilesLayer1[newIndex] = TILE_BURNED_CHIP;
	}
	else
	{
		tilesLayer1[newIndex] = 0x6C + dir;
	}
	playerX = newX;
	playerY = newY;

	return true;
}

bool findNextTeleport(uint8_t objectX, uint8_t objectY, uint16_t teleportIndex, uint8_t direction, uint8_t monsterIndex)
{
	// player  0
	// block   1
	// monster 2
	int type = 0;
	if (getTile(objectX, objectY, 1) == TILE_MOVABLE_BLOCK)
	{
		type = 1;
	}
	else if (playerX == objectX && playerY == objectY)
	{
		type = 0;
	}
	else if (getTile(objectX, objectY, 1) >= 0x40 && getTile(objectX, objectY, 1) <= 0x63)
	{
		type = 2;
	}
	else
	{
		return false;
	}

	int8_t origPlayerX = playerX;
	int8_t origPlayerY = playerY;

	// Find current teleport in list
	bool foundStartTeleport = false;
	uint16_t startTeleport = 0;
	uint16_t i;
	for (i = 0; i < cur_level->numberOfTeleports; i++)
	{
		if (cur_level->teleports[i] == teleportIndex)
		{
			foundStartTeleport = true;
			startTeleport = i;
			break;
		}
	}
	
	if (!foundStartTeleport)
		return false;

	if (startTeleport == 0)
		i = cur_level->numberOfTeleports - 1;
	else
		i = startTeleport - 1;

	while (1)
	{
		uint8_t tempX = cur_level->teleports[i] % LEVELSIZE;
		uint8_t tempY = cur_level->teleports[i] / LEVELSIZE;

		int8_t dirX;
		int8_t dirY;
		getDirectionXY(&dirX, &dirY, direction);

		if (getTile(tempX, tempY, 1) == TILE_TELEPORT)
		{
			if (type == 0)
			{
				playerX = tempX;
				playerY = tempY;

				// Check if teleport is OK to teleport to
				if (movePlayer(playerX + dirX, playerY + dirY, true))
				{
					tilesLayer2[cur_level->teleports[i]] = tilesLayer1[cur_level->teleports[i]];
					tilesLayer1[cur_level->teleports[i]] = 0x6C + playerSlideDirection;
					return true;
				}
			}
			else if (type == 1)
			{
				// Check if teleport is OK to teleport to
				if (moveBlock(tempX, tempY, tempX + dirX, tempY + dirY, false, true, false))
				{
					tilesLayer2[cur_level->teleports[i]] = tilesLayer1[cur_level->teleports[i]];
					tilesLayer1[cur_level->teleports[i]] = TILE_MOVABLE_BLOCK;
					
					uint16_t oldIndex = objectX + objectY * LEVELSIZE;
					tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
					tilesLayer2[oldIndex] = 0x00;

					if (slidingFind(objectX, objectY))
						slidingDeleteCur();

					slidingInsert(direction, tempX, tempY);

					return true;
				}
			}
			else
			{
				uint8_t monsterOrigX = monsters[monsterIndex].x;
				uint8_t monsterOrigY = monsters[monsterIndex].y;
				monsters[monsterIndex].x = tempX;
				monsters[monsterIndex].y = tempY;

				if (moveMonster(monsterIndex, true))
				{
					tilesLayer2[cur_level->teleports[i]] = tilesLayer1[cur_level->teleports[i]];
					tilesLayer1[cur_level->teleports[i]] = monsterBaseTile[monsters[monsterIndex].type] + direction;

					uint16_t oldIndex = objectX + objectY * LEVELSIZE;
					tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
					tilesLayer2[oldIndex] = 0x00;

					monsters[monsterIndex].sliding = true;

					return true;
				}

				monsters[monsterIndex].x = monsterOrigX;
				monsters[monsterIndex].y = monsterOrigY;
			}
		}

		if (i == startTeleport)
			break;

		if (i == 0)
			i = cur_level->numberOfTeleports - 1;
		else
			i--;
	}

	if (type == 0)
	{
		playerX = origPlayerX;
		playerY = origPlayerY;
	}
	return false;
}

bool isIce(uint8_t tile)
{
	if (tile == TILE_ICE ||
		tile == TILE_ICE_CORNER_SE || tile == TILE_ICE_CORNER_SW ||
		tile == TILE_ICE_CORNER_NW || tile == TILE_ICE_CORNER_NE)
	{
		return true;
	}

	return false;
}

bool isForceFloor(uint8_t tile)
{
	if (tile == TILE_FORCE_S || tile == TILE_FORCE_N ||
		tile == TILE_FORCE_E || tile == TILE_FORCE_W ||
		tile == TILE_FORCE_RANDOM)
	{
		return true;
	}

	return false;
}

bool isMonsterTrapped(uint8_t monsterIndex)
{
	uint8_t x = monsters[monsterIndex].x;
	uint8_t y = monsters[monsterIndex].y;
	uint16_t index = x + y * LEVELSIZE;

	return (getTile(x, y, 2) == TILE_TRAP && !isTrapOpen(index));
}

bool moveMonster(uint8_t monsterIndex, bool tryMove)
{
	if (monsters[monsterIndex].type == MONSTER_NONE)
	{
		return false;
	}

	uint8_t direction = monsters[monsterIndex].direction;
	int8_t oldX = monsters[monsterIndex].x;
	int8_t oldY = monsters[monsterIndex].y;
	int8_t newX = 0;
	int8_t newY = 0;
	bool spawning = monsters[monsterIndex].spawning;
	bool shouldPressTankButton = false;

	uint16_t oldIndex = 0;
	if (!spawning)
		oldIndex = oldX + oldY * LEVELSIZE;

	bool hitWall = false;

	if (!spawning)
	{
		// Do not move monsters on top of clone machines
		if (tilesLayer2[oldIndex] == TILE_CLONE_MACHINE)
		{
			return false;
		}

		if (isMonsterTrapped(monsterIndex))
		{
			return false;
		}

		// Do not allow to change direction on teleports
		if (getTile(oldX, oldY, 2) == TILE_TELEPORT)
		{
			direction = monsters[monsterIndex].direction;
		}

		if (monsters[monsterIndex].sliding)
		{
			if (tilesLayer2[oldIndex] == TILE_FORCE_S)
			{
				direction = DIRECTION_SOUTH;
			}
			else if (tilesLayer2[oldIndex] == TILE_FORCE_N)
			{
				direction = DIRECTION_NORTH;
			}
			else if (tilesLayer2[oldIndex] == TILE_FORCE_E)
			{
				direction = DIRECTION_EAST;
			}
			else if (tilesLayer2[oldIndex] == TILE_FORCE_W)
			{
				direction = DIRECTION_WEST;
			}
			else if ((tilesLayer2[oldIndex] == TILE_ICE_CORNER_SE && (direction == DIRECTION_NORTH || direction == DIRECTION_WEST)) ||
				(tilesLayer2[oldIndex] == TILE_ICE_CORNER_SW && (direction == DIRECTION_NORTH || direction == DIRECTION_EAST)) ||
				(tilesLayer2[oldIndex] == TILE_ICE_CORNER_NW && (direction == DIRECTION_SOUTH || direction == DIRECTION_EAST)) ||
				(tilesLayer2[oldIndex] == TILE_ICE_CORNER_NE && (direction == DIRECTION_SOUTH || direction == DIRECTION_WEST)))
			{
				direction = iceCornerGetNewDirection(tilesLayer2[oldIndex], direction);
			}
		}

		if (tilesLayer2[oldIndex] == TILE_BLOCKED_N && direction == DIRECTION_NORTH)
			hitWall = true;
		if (tilesLayer2[oldIndex] == TILE_BLOCKED_W && direction == DIRECTION_WEST)
			hitWall = true;
		if (tilesLayer2[oldIndex] == TILE_BLOCKED_S && direction == DIRECTION_SOUTH)
			hitWall = true;
		if (tilesLayer2[oldIndex] == TILE_BLOCKED_E && direction == DIRECTION_EAST)
			hitWall = true;
		if (tilesLayer2[oldIndex] == TILE_BLOCKED_SE && (direction == DIRECTION_EAST || direction == DIRECTION_SOUTH))
			hitWall = true;
	}

	newX = getDirectionX(direction) + oldX;
	newY = getDirectionY(direction) + oldY;

	uint16_t newIndex = newX + newY * LEVELSIZE;

	if (newX < 0 || newY < 0 || newX >= LEVELSIZE || newY >= LEVELSIZE)
	{
		hitWall = true;
	}
	else if (!hitWall)
	{
		uint8_t tile = tilesLayer1[newIndex];

		// If tile is Chip, check layer 2 instead
		if ((tile >= TILE_PLAYER_N && tile <= TILE_PLAYER_E) || (tile >= TILE_SWIMMING_N && tile <= TILE_SWIMMING_E))
			tile = tilesLayer2[newIndex];

		bool tempSlide = monsters[monsterIndex].sliding;

		switch (tile)
		{
			case TILE_FLOOR:
				tempSlide = false;
				break;
			case TILE_WATER:
				if (monsters[monsterIndex].type == MONSTER_GLIDER)
				{
					tempSlide = false;
				}
				else
				{
					if (!tryMove)
						monsters[monsterIndex].type = MONSTER_NONE;

					if (!spawning && !tryMove)
					{
						tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
						tilesLayer2[oldIndex] = 0x00;
					}
					return true;
				}
				break;
			case TILE_FIRE:
				if (monsters[monsterIndex].type == MONSTER_BUG ||
					monsters[monsterIndex].type == MONSTER_WALKER)
				{
					hitWall = true;
				}
				else if (monsters[monsterIndex].type == MONSTER_FIREBALL)
				{
					tempSlide = false;
				}
				else
				{
					if (!tryMove)
						monsters[monsterIndex].type = MONSTER_NONE;

					if (!spawning && !tryMove)
					{
						tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
						tilesLayer2[oldIndex] = 0x00;
					}
					return true;
				}
				break;
			case TILE_BLOCKED_N:
				if (direction == DIRECTION_SOUTH)
					hitWall = true;
				else
					tempSlide = false;
				break;
			case TILE_BLOCKED_W:
				if (direction == DIRECTION_EAST)
					hitWall = true;
				else
					tempSlide = false;
				break;
			case TILE_BLOCKED_S:
				if (direction == DIRECTION_NORTH)
					hitWall = true;
				else
					tempSlide = false;
				break;
			case TILE_BLOCKED_E:
				if (direction == DIRECTION_WEST)
					hitWall = true;
				else
					tempSlide = false;
				break;
			case TILE_ICE:
			case TILE_FORCE_S:
			case TILE_FORCE_N:
			case TILE_FORCE_E:
			case TILE_FORCE_W:
				tempSlide = true;
				break;
			case TILE_ICE_CORNER_SE:
				if (direction == DIRECTION_NORTH || direction == DIRECTION_WEST)
					tempSlide = true;
				else
					hitWall = true;
				break;
			case TILE_ICE_CORNER_SW:
				if (direction == DIRECTION_NORTH || direction == DIRECTION_EAST)
					tempSlide = true;
				else
					hitWall = true;
				break;
			case TILE_ICE_CORNER_NW:
				if (direction == DIRECTION_SOUTH || direction == DIRECTION_EAST)
					tempSlide = true;
				else
					hitWall = true;
				break;
			case TILE_ICE_CORNER_NE:
				if (direction == DIRECTION_SOUTH || direction == DIRECTION_WEST)
					tempSlide = true;
				else
					hitWall = true;
				break;
			case TILE_TOGGLE_BUTTON:
				if (!tryMove)
					toggleAllToggleWalls();
				tempSlide = false;
				break;
			case TILE_CLONE_BUTTON:
				if (!tryMove)
					clonePressInsert(newX, newY);
				tempSlide = false;
				break;
			case TILE_TOGGLE_OPEN:
				tempSlide = false;
				break;
			case TILE_TRAP_BUTTON:
				if (!tryMove)
					playSound(SOUND_BUTTON);
				tempSlide = false;
				break;
			case TILE_TANK_BUTTON:
				if (!tryMove)
				{
					shouldPressTankButton = true;
				}
				tempSlide = false;
				break;
			case TILE_TELEPORT:
				if (tryMove)
					return true;

				monsters[monsterIndex].spawning = false;
				if (findNextTeleport(oldX, oldY, newIndex, direction, monsterIndex))
				{
					return true;
				}
				else
				{
					tempSlide = true;
				}
				break;
			case TILE_BOMB:
				if (tryMove)
					return true;

				playSound(SOUND_BOMB);

				monsters[monsterIndex].type = MONSTER_NONE;
				tilesLayer1[newIndex] = 0x00;
				if (!spawning)
				{
					tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
					tilesLayer2[oldIndex] = 0x00;
				}
				return true;
				break;
			case TILE_TRAP:
				tempSlide = false;
				break;
			case TILE_BLOCKED_SE:
				if (direction == DIRECTION_NORTH || direction == DIRECTION_WEST)
					hitWall = true;
				else
					tempSlide = false;
				break;
			default:
				hitWall = true;
		}

		if (!tryMove)
			monsters[monsterIndex].sliding = tempSlide;
	}

	if (tryMove)
	{
		return !hitWall;
	}

	if (hitWall)
	{
		if (spawning)
		{
			// Remove again from list, as we couldn't spawn it
			monsters[monsterIndex].type = MONSTER_NONE;
			return false;
		}
		else
		{
			// Stuck on teleporter
			if (getTile(oldX, oldY, 2) == TILE_TELEPORT)
			{
				return false;
			}

			if ((tilesLayer1[oldIndex] >= 0x4C && tilesLayer1[oldIndex] <= 0x4F) ||
				(tilesLayer1[oldIndex] >= 0x54 && tilesLayer1[oldIndex] <= 0x57))
			{
				tilesLayer1[oldIndex] = monsterBaseTile[monsters[monsterIndex].type] + direction; // direction here works as an offset
				monsters[monsterIndex].direction = direction;
			}

			if (monsters[monsterIndex].sliding)
			{
				if (!spawning)
				{
					uint8_t oldTile = tilesLayer2[oldIndex];
					if ((oldTile >= 0x1A && oldTile <= 0x1D) || oldTile == 0x0C) // Ice
					{
						// Update direction to opposite
						tilesLayer1[oldIndex] = monsterBaseTile[monsters[monsterIndex].type] + (direction + 2) % 4;
						monsters[monsterIndex].direction = (direction + 2) % 4;
					}
				}
			}

			return false;
		}
	}

	if ((tilesLayer1[newIndex] >= 0x6C && tilesLayer1[newIndex] <= 0x6F) ||
		(tilesLayer1[newIndex] >= 0x3C && tilesLayer1[newIndex] <= 0x3F))
	{
		gameOverReason = gameOverMessageMonster;
		gameModeSub = GAME_MODE_GAME_GAMEOVER;
	}

	if (!spawning)
	{
		tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
		tilesLayer2[oldIndex] = 0x00;
	}
	tilesLayer2[newIndex] = tilesLayer1[newIndex];
	tilesLayer1[newIndex] = monsterBaseTile[monsters[monsterIndex].type] + direction; // direction here works as an offset
	monsters[monsterIndex].direction = direction;

	monsters[monsterIndex].x = newX;
	monsters[monsterIndex].y = newY;
	monsters[monsterIndex].spawning = false;

	if (shouldPressTankButton)
		pressTankButton();

	return true;
}

bool moveBlock(int8_t oldX, int8_t oldY, int8_t newX, int8_t newY, bool spawning, bool tryMove, bool playerPushed)
{
	bool hitWall = false;
	bool isSliding = slidingFind(oldX, oldY);

	uint8_t dir = getDirection(oldX, oldY, newX, newY);

	if (isSliding && playerPushed)
	{
		if (dir == slidingCur->direction)
		{
			return false;
		}
		else if (dir == ((slidingCur->direction + 2) % 4)) // Opposite direction
		{
			setTile(playerX, playerY, 1, TILE_MOVABLE_BLOCK);
			if (!spawning)
			{
				// Restore tiles at old position
				setTile(oldX, oldY, 1, getTile(oldX, oldY, 2));
				setTile(oldX, oldY, 2, TILE_FLOOR);
			}
			gameOverReason = gameOverMessageBlock;
			gameModeSub = GAME_MODE_GAME_GAMEOVER;
			return false;
		}
	}

	uint16_t newIndex = newX + newY * LEVELSIZE;
	uint16_t oldIndex = 0;
	if (!spawning)
	{
		oldIndex = oldX + oldY * LEVELSIZE;
	}

	if (newX < 0 || newY < 0 || newX >= LEVELSIZE || newY >= LEVELSIZE)
	{
		hitWall = true;
	}
	else
	{
		uint8_t newTile = tilesLayer1[newIndex];

		if (!spawning)
		{
			if (tilesLayer2[oldIndex] == TILE_TRAP && !isTrapOpen(oldIndex))
			{
				hitWall = true;
			}
			if (tilesLayer2[oldIndex] == TILE_BLOCKED_N && dir == DIRECTION_NORTH)
				hitWall = true;
			if (tilesLayer2[oldIndex] == TILE_BLOCKED_W && dir == DIRECTION_WEST)
				hitWall = true;
			if (tilesLayer2[oldIndex] == TILE_BLOCKED_S && dir == DIRECTION_SOUTH)
				hitWall = true;
			if (tilesLayer2[oldIndex] == TILE_BLOCKED_E && dir == DIRECTION_EAST)
				hitWall = true;
			if (tilesLayer2[oldIndex] == TILE_BLOCKED_SE && (dir == DIRECTION_EAST || dir == DIRECTION_SOUTH))
				hitWall = true;
		}

		if (!hitWall)
		{
			switch (newTile)
			{
				case TILE_FLOOR:
					break;
				case TILE_WATER:
					if (!tryMove)
					{
						playSound(SOUND_SPLASH);

						if (!spawning)
						{
							// This is done here because we return. For other tiles this is done later in the function.
							tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
							tilesLayer2[oldIndex] = TILE_FLOOR;
						}
						tilesLayer1[newIndex] = TILE_DIRT;
						if (isSliding == true)
							slidingDeleteCur();
					}
					return true;
				case TILE_FIRE:
					break;
				case TILE_BLOCKED_N:
					if (dir == DIRECTION_SOUTH)
						hitWall = true;
					break;
				case TILE_BLOCKED_W:
					if (dir == DIRECTION_EAST)
						hitWall = true;
					break;
				case TILE_BLOCKED_S:
					if (dir == DIRECTION_NORTH)
						hitWall = true;
					break;
				case TILE_BLOCKED_E:
					if (dir == DIRECTION_WEST)
						hitWall = true;
					break;
				case TILE_ICE:
					if (tryMove)
						break;
					if (isSliding == true)
					{
						slidingCur->direction = dir; // Normally the same, but to support ramming
					}
					else
					{
						slidingInsert(dir, newX, newY);
						isSliding = true;
						slidingFind(newX, newY); // To set slidingCur
					}
					break;
				case TILE_FORCE_S:
					if (tryMove)
						break;
					if (isSliding == true)
					{
						slidingCur->direction = DIRECTION_SOUTH;
					}
					else
					{
						slidingInsert(DIRECTION_SOUTH, newX, newY);
						isSliding = true;
						slidingFind(newX, newY); // To set slidingCur
					}
					break;
				case TILE_FORCE_N:
					if (tryMove)
						break;
					if (isSliding == true)
					{
						slidingCur->direction = DIRECTION_NORTH;
					}
					else
					{
						slidingInsert(DIRECTION_NORTH, newX, newY);
						isSliding = true;
						slidingFind(newX, newY); // To set slidingCur
					}
					break;
				case TILE_FORCE_E:
					if (tryMove)
						break;
					if (isSliding == true)
					{
						slidingCur->direction = DIRECTION_EAST;
					}
					else
					{
						slidingInsert(DIRECTION_EAST, newX, newY);
						isSliding = true;
						slidingFind(newX, newY); // To set slidingCur
					}
					break;
				case TILE_FORCE_W:
					if (tryMove)
						break;
					if (isSliding == true)
					{
						slidingCur->direction = DIRECTION_WEST;
					}
					else
					{
						slidingInsert(DIRECTION_WEST, newX, newY);
						isSliding = true;
						slidingFind(newX, newY); // To set slidingCur
					}
					break;
				case TILE_EXIT:
					break;
				case TILE_ICE_CORNER_SE:
				case TILE_ICE_CORNER_SW:
				case TILE_ICE_CORNER_NW:
				case TILE_ICE_CORNER_NE:
					if ((newTile == 0x1A && (dir == DIRECTION_NORTH || dir == DIRECTION_WEST)) ||
						(newTile == 0x1B && (dir == DIRECTION_NORTH || dir == DIRECTION_EAST)) ||
						(newTile == 0x1C && (dir == DIRECTION_SOUTH || dir == DIRECTION_EAST)) ||
						(newTile == 0x1D && (dir == DIRECTION_SOUTH || dir == DIRECTION_WEST)))
					{
						if (tryMove)
							break;

						uint8_t newDir = iceCornerGetNewDirection(newTile, dir);
						
						if (isSliding == true)
						{
							slidingCur->direction = newDir;
						}
						else
						{
							slidingInsert(newDir, newX, newY);
							isSliding = true;
							slidingFind(newX, newY); // To set slidingCur
						}
					}
					else
					{
						hitWall = true;
					}
					break;
				case TILE_TOGGLE_BUTTON:
					if (!tryMove)
						toggleAllToggleWalls();
					break;
				case TILE_CLONE_BUTTON:
					if (!tryMove)
						clonePressInsert(newX, newY);
					break;
				case TILE_TOGGLE_OPEN:
					break;
				case TILE_TRAP_BUTTON:
					if (!tryMove)
						playSound(SOUND_BUTTON);
					break;
				case TILE_TANK_BUTTON:
					if (!tryMove)
						pressTankButton();
					break;
				case TILE_TELEPORT:
					if (tryMove)
						return true;

					if (findNextTeleport(oldX, oldY, newIndex, dir, 0))
					{
						return true;
					}
					else
					{
						if (isSliding == true)
						{
							slidingCur->direction = dir; // Normally the same, but to support ramming
						}
						else
						{
							slidingInsert(dir, newX, newY);
							isSliding = true;
							slidingFind(newX, newY); // To set slidingCur
						}
					}
					break;
				case TILE_BOMB:
					if (!tryMove)
					{
						playSound(SOUND_BOMB);
						if (!spawning)
						{
							tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
							tilesLayer2[oldIndex] = TILE_FLOOR;
						}
						tilesLayer1[newIndex] = TILE_FLOOR;
						if (isSliding == true && !tryMove)
							slidingDeleteCur();
					}
					return true;
				case TILE_TRAP:
					break;
				case TILE_GRAVEL:
					break;
				case TILE_HINT:
					break;
				case TILE_BLOCKED_SE:
					if (dir == DIRECTION_NORTH || dir == DIRECTION_WEST)
						hitWall = true;
					break;
				case TILE_FORCE_RANDOM:
					if (tryMove)
						break;
					if (isSliding == true)
					{
						slidingCur->direction = randomDirection();
					}
					else
					{
						slidingInsert(randomDirection(), newX, newY);
						isSliding = true;
						slidingFind(newX, newY); // To set slidingCur
					}
					break;
				case TILE_SWIMMING_N:
				case TILE_SWIMMING_W:
				case TILE_SWIMMING_S:
				case TILE_SWIMMING_E:
					gameOverReason = gameOverMessageBlock;
					gameModeSub = GAME_MODE_GAME_GAMEOVER;
					break;
				case 0x64:
				case 0x65:
				case 0x66:
				case 0x67:
				case 0x68:
				case 0x69:
				case 0x6A:
				case 0x6B:
					break;
				case TILE_PLAYER_N:
				case TILE_PLAYER_W:
				case TILE_PLAYER_S:
				case TILE_PLAYER_E:
					gameOverReason = gameOverMessageBlock;
					gameModeSub = GAME_MODE_GAME_GAMEOVER;
					break;
				default:
					hitWall = true;
			}
		}
	}

	if (tryMove)
	{
		return !hitWall;
	}

	if (hitWall)
	{
		if (isSliding && tilesLayer1[newIndex] != TILE_TRAP) // It will simply continue to slide in the same direction when trap opens
		{
			if (!spawning)
			{
				if (playerPushed)
				{
					// Ramming, remove from slide list
					slidingDeleteCur();
				}
				else
				{
					uint8_t oldTile = tilesLayer2[oldIndex];
					if ((oldTile >= 0x1A && oldTile <= 0x1D) || oldTile == 0x0C)
					{
						// Bounce
						slidingCur->direction = slidingHitWallNewDirection(oldTile, dir);
					}
					else if (oldTile == TILE_FORCE_RANDOM)
					{
						slidingCur->direction = randomDirection();
					}
				}
			}
		}

		return false;
	}

	// If block is sliding and the destination is neither ice nor force floor
	if (isSliding == true && !isIce(tilesLayer1[newIndex]) && !isForceFloor(tilesLayer1[newIndex]) && tilesLayer1[newIndex] != TILE_TRAP)
	{
		slidingDeleteCur();
	}
	else if (isSliding == true)
	{
		// Update after movement
		if (slidingCur == NULL)
		{
			errorMsg = "slidingCur is NULL!";
			gameMode = GAME_MODE_ERROR;
		}
		else
		{
			slidingCur->x = newX;
			slidingCur->y = newY;
		}
	}

	// Move tile in destination to layer 2
	if (tilesLayer1[newIndex] != TILE_FLOOR)
		tilesLayer2[newIndex] = tilesLayer1[newIndex];
	tilesLayer1[newIndex] = TILE_MOVABLE_BLOCK;

	if (!spawning)
	{
		// Restore tiles at old position
		tilesLayer1[oldIndex] = tilesLayer2[oldIndex];
		tilesLayer2[oldIndex] = TILE_FLOOR;
	}

	return true;
}

uint8_t getDirection(int8_t oldX, int8_t oldY, int8_t newX, int8_t newY)
{
	if (newX > oldX)
		return DIRECTION_EAST;
	else if (newX < oldX)
		return DIRECTION_WEST;
	else if (newY > oldY)
		return DIRECTION_SOUTH;
	else
		return DIRECTION_NORTH;
}

// oldTile is type of ice. Only use this function when player is leaving ice and collides with a wall.
uint8_t slidingHitWallNewDirection(uint8_t oldTile, uint8_t dir)
{
	if (oldTile == 0x1A)
	{
		if (dir == DIRECTION_EAST)
			return DIRECTION_SOUTH;
		else
			return DIRECTION_EAST;
	}
	else if (oldTile == 0x1B)
	{
		if (dir == DIRECTION_WEST)
			return DIRECTION_SOUTH;
		else
			return DIRECTION_WEST;
	}
	else if (oldTile == 0x1C)
	{
		if (dir == DIRECTION_WEST)
			return DIRECTION_NORTH;
		else
			return DIRECTION_WEST;
	}
	else if (oldTile == 0x1D)
	{
		if (dir == DIRECTION_EAST)
			return DIRECTION_NORTH;
		else
			return DIRECTION_EAST;
	}
	else
	{
		// Opposite direction
		return (dir + 2) % 4;
	}
}

uint8_t iceCornerGetNewDirection(uint8_t tile, uint8_t dir)
{
	uint8_t newDir;

	if (tile == 0x1A)
	{
		if (dir == DIRECTION_NORTH)
		{
			newDir = DIRECTION_EAST;
		}
		else
		{
			newDir = DIRECTION_SOUTH;
		}
	}
	else if (tile == 0x1B)
	{
		if (dir == DIRECTION_NORTH)
		{
			newDir = DIRECTION_WEST;
		}
		else
		{
			newDir = DIRECTION_SOUTH;
		}
	}
	else if (tile == 0x1C)
	{
		if (dir == DIRECTION_SOUTH)
		{
			newDir = DIRECTION_WEST;
		}
		else
		{
			newDir = DIRECTION_NORTH;
		}
	}
	else
	{
		if (dir == DIRECTION_SOUTH)
		{
			newDir = DIRECTION_EAST;
		}
		else
		{
			newDir = DIRECTION_NORTH;
		}
	}

	return newDir;
}

bool getCloneMachineXY(uint8_t buttonX, uint8_t buttonY, uint8_t *cloneX, uint8_t *cloneY)
{
	uint8_t i;
	for (i = 0; i < 31; i++)
	{
		if (cloneMachineConnections[i].buttonX == buttonX && cloneMachineConnections[i].buttonY == buttonY)
		{
			*cloneX = cloneMachineConnections[i].cloneX;
			*cloneY = cloneMachineConnections[i].cloneY;
			return true;
		}
	}

	return false;
}

void pressCloneButton(uint8_t x, uint8_t y)
{
	uint8_t cx = 0;
	uint8_t cy = 0;

	if (!getCloneMachineXY(x, y, &cx, &cy))
		return;

	uint16_t ti = cx + cy * LEVELSIZE;
	if (tilesLayer2[ti] == TILE_CLONE_MACHINE)
	{
		if ((tilesLayer1[ti] >= 0x0E && tilesLayer1[ti] <= 0x11) ||
			(tilesLayer1[ti] >= 0x40 && tilesLayer1[ti] <= 0x63))
		{
			uint8_t dir = 4; // invalid
			int8_t newX = cx;
			int8_t newY = cy;

			if (tilesLayer1[ti] >= 0x0E && tilesLayer1[ti] <= 0x11) // Clone Block
			{
				dir = (tilesLayer1[ti] - 2) % 4;
			}
			else // Monsters
			{
				// BUG, TEETH, PARAMECIUM
				// FIXME this is a windows version specific bug I might try to emulate...
				// But it needs more work, so it's not used for now
				/*if ((tilesLayer1[ti] >= 0x40 && tilesLayer1[ti] <= 0x43) ||
					(tilesLayer1[ti] >= 0x54 && tilesLayer1[ti] <= 0x57) ||
					(tilesLayer1[ti] >= 0x60 && tilesLayer1[ti] <= 0x63))
				{
					// Use the last monster in the monster list
					uint8_t j;
					for (j = 0; j < 128; j++)
					{
						if (monsters[j].type != MONSTER_NONE)
						{
							dir = monsters[j].direction;
						}
					}

					// If no monster is found in the list, no monster is spawned/cloned.
					if (dir == 4)
						return;
				}*/
				//else // Other monsters
				//{
					dir = getMonsterDirectionFromTile(getTile(cx, cy, 1));
				//}
			}

			switch (dir)
			{
				case DIRECTION_NORTH:
					newY--;
					break;
				case DIRECTION_WEST:
					newX--;
					break;
				case DIRECTION_SOUTH:
					newY++;
					break;
				default:
					newX++;
					break;
			}

			if (tilesLayer1[ti] >= 0x0E && tilesLayer1[ti] <= 0x11) // Clone Block
			{
				moveBlock(cx, cy, newX, newY, true, false, false);
			}
			else // Monsters
			{
				uint8_t type = tilesLayer1[ti] - 0x40; // 0x40 = first monster
				type = (type / 4) + 1;
				uint8_t m = monsterBaseTile[type];
				m += dir;
				addMonster(type, cx, cy, dir);
			}
		}
	}
}

void pressTankButton()
{
	playSound(SOUND_BUTTON);

	uint8_t i;
	for (i = 0; i < 128; i++)
	{
		if (!monsters[i].sliding && monsters[i].type == MONSTER_TANK)
		{
			uint8_t tile = getTile(monsters[i].x, monsters[i].y, 1);
			uint8_t tile2 = getTile(monsters[i].x, monsters[i].y, 2);
			if (tile >= 0x4C && tile <= 0x4F && tile2 != TILE_CLONE_MACHINE)
			{
				monsters[i].moving = MOVING_SHOULDMOVE;
				uint8_t dir = monsters[i].direction;
				dir = (dir + 1) % 4;
				setTile(monsters[i].x, monsters[i].y, 1, monsterBaseTile[MONSTER_TANK] + dir);
			}
		}
	}
}

bool isTrapOpen(uint16_t index)
{
	uint8_t x = index % LEVELSIZE;
	uint8_t y = index / LEVELSIZE;

	// Find trap in list
	uint8_t i;
	for (i = 0; i < 19; i++)
	{
		if (traps[i].trapX == x && traps[i].trapY == y)
		{
			return traps[i].open;
		}
	}

	// If trap has no connection, it will trap
	return false;
}

void updateTraps()
{
	uint8_t i;

	for (i = 0; i < 19; i++)
	{
		if (traps[i].trapX == 0xFF)
		{
			break;
		}

		if (getTile(traps[i].trapX, traps[i].trapY, 1) == TILE_TRAP)
		{
			traps[i].open = false;
		}
	}
	for (i = 0; i < 19; i++)
	{
		if (trapButtons[i].buttonX == 0xFF)
		{
			break;
		}

		if (getTile(trapButtons[i].buttonX, trapButtons[i].buttonY, 2) == TILE_TRAP_BUTTON)
		{
			traps[trapButtons[i].trapIndex].open = true;
		}
	}
}

void toggleAllToggleWalls()
{
	uint16_t i;
	for (i = 0; i < (LEVELSIZE * LEVELSIZE); i++)
	{
		if (tilesLayer2[i] == 0x31)
		{
			continue;
		}

		if (tilesLayer1[i] == 0x25)
		{
			tilesLayer1[i] = 0x26;
		}
		else if (tilesLayer1[i] == 0x26)
		{
			tilesLayer1[i] = 0x25;
		}
		else if ((tilesLayer1[i] >= 0x40 && tilesLayer1[i] <= 0x63) ||
			(tilesLayer1[i] >= 0x6C && tilesLayer1[i] <= 0x6F) ||
			tilesLayer1[i] == 0x0A)
		{
			if (tilesLayer2[i] == 0x25)
			{
				tilesLayer2[i] = 0x26;
			}
			else if (tilesLayer2[i] == 0x26)
			{
				tilesLayer2[i] = 0x25;
			}
		}
	}
}

uint32_t modulus(int32_t number, uint32_t modulus) // Positive modulus (-1 % 2 == 1)
{
	while (number < 0)
	{
		number += modulus;
	}

	return number % modulus;
}

void slidingProcess()
{
	listNode* curNode = slidingBlocks.first;
	sliding* curSlide;
	while (curNode != NULL)
	{
		curSlide = curNode->data;

		// 2 times, one retry if needed (to make collisions instant)
		uint8_t i;
		for (i = 0; i < 2; i++)
		{
			uint8_t newX;
			uint8_t newY;
			switch (curSlide->direction)
			{
				case DIRECTION_NORTH:
					newX = curSlide->x;
					newY = curSlide->y - 1;
					break;
				case DIRECTION_WEST:
					newX = curSlide->x - 1;
					newY = curSlide->y;
					break;
				case DIRECTION_SOUTH:
					newX = curSlide->x;
					newY = curSlide->y + 1;
					break;
				default:
					newX = curSlide->x + 1;
					newY = curSlide->y;
			}

			if (moveBlock(curSlide->x, curSlide->y, newX, newY, false, false, false))
			{
				break;
			}
		}

		curNode = curNode->next;
	}
}

void slidingInsert(uint8_t direction, uint8_t x, uint8_t y)
{
	sliding* a = (sliding*)malloc(sizeof(sliding));
	a->direction = direction;
	a->x = x;
	a->y = y;

	listInsertLast(&slidingBlocks, a);
}

void slidingDeleteCur()
{
	if (slidingBlocks.first == NULL || slidingCur == NULL)
		return;

	listDelete(&slidingBlocks, slidingCur);
}

bool slidingFind(uint8_t x, uint8_t y)
{
	listNode* curNode = slidingBlocks.first;
	sliding* curSlide;
	while (curNode != NULL)
	{
		curSlide = curNode->data;

		if (x == curSlide->x && y == curSlide->y)
		{
			slidingCur = curSlide;
			return true;
		}

		curNode = curNode->next;
	}

	return false;
}

void clonePressInsert(uint8_t x, uint8_t y)
{
	playSound(SOUND_BUTTON);

	uint8_t cx = 0;
	uint8_t cy = 0;

	if (!getCloneMachineXY(x, y, &cx, &cy))
		return;

	position* a = (position*)malloc(sizeof(position));
	a->x = x;
	a->y = y;

	if (getTile(cx, cy, 1) >= 0x0E && getTile(cx, cy, 1) <= 0x11)
		listInsertLast(&cloneButtonPressesBlocks, a);
	else
		listInsertLast(&cloneButtonPressesMonsters, a);
}

uint8_t randomDirection()
{
	return _rand8() % 4;
}

uint8_t _rand8()
{
	uint32_t tempRand = rand();
	return ((tempRand >> 24) ^ (tempRand >> 16) ^ (tempRand >> 8) ^ tempRand);
}

// FIXME move this into a game state and use the main loop
void checkAndFixSpiralsCorruption()
{
	FILE* datFile = fopen(chipsDatFileName, "rb");
	if (datFile == NULL)
	{
		return;
	}

	unsigned int crc = getcrc(datFile);
	if (crc == 0xA92394BC)
	{
		bool show = true;
		bool patch = false;
		while (show)
		{
			clearScreen(COLOR_BLACK);
			gfx_font_print(10, 10, gameFont, "Chip World has detected that the chips.dat file\nyou're using has the \"Spirals Corruption\" issue.\n\nRead more about the issue at\nhttp://chipschallenge.wikia.com/wiki/\nSpirals_corruption\n\n\n\nDo you want to patch chips.dat and fix this?\n\n(A) Yes, please patch chips.dat\n(B) No, don't patch");
			flipDisplay();
			OSTimeDly(1);
			controlUpdate();
			if (controlJustPressed(CONTROL_BUTTON1))
			{
				patch = true;
				show = false;
			}
			if (controlJustPressed(CONTROL_BUTTON2))
			{
				show = false;
			}
		}

		if (patch)
		{
			clearScreen(COLOR_BLACK);
			gfx_font_print(10, 10, gameFont, "Patching...");
			flipDisplay();

			// Fix problem
			//freopen(chipsDatFileName, "rb+", datFile);
			fclose(datFile);
			datFile = fopen(chipsDatFileName, "rb+");
			if (datFile == NULL)
			{
				show = true;
				while (show)
				{
					clearScreen(COLOR_BLACK);
					gfx_font_print(10, 10, gameFont, "Error: Could not write to chips.dat.\nIs it write protected?\nThe bug has not been fixed...\n\nPress A or B to continue.");
					flipDisplay();
					OSTimeDly(1);
					controlUpdate();
					if (controlJustPressed(CONTROL_BUTTON1) || controlJustPressed(CONTROL_BUTTON2))
					{
						show = false;
					}
				}
			}
			else
			{
				fseek(datFile, 0xec5c, SEEK_SET);
				putc(0, datFile);
				fclose(datFile);
				show = true;
				while (show)
				{
					clearScreen(COLOR_BLACK);
					gfx_font_print(10, 10, gameFont, "Chips.dat patched OK!\n\nPress A or B to continue.");
					flipDisplay();
					OSTimeDly(1);
					controlUpdate();
					if (controlJustPressed(CONTROL_BUTTON1) || controlJustPressed(CONTROL_BUTTON2))
					{
						show = false;
					}
				}
			}
		}
		else
		{
			fclose(datFile);
		}
	}

	drawSplash();
	flipDisplay();
}

void longTaskBegin()
{
	mixerPause(true);
}

void longTaskEnd()
{
	mixerPause(false);
	gameTicksDoNotCatchUp = true;
}

void update()
{
	if (controlPressed(CONTROL_BUTTON6))
	{
		selectButtonCurHoldTime++;
	}
	else
	{
		selectButtonCurHoldTime = 0;
	}

	if (selectButtonCurHoldTime == SELECT_BUTTON_HOLD_TIME)
	{
		gameScreenshot();
		screenshotTakenShowTime = 90;
	}

	if (cyclesSinceNoDpad < 100)
		cyclesSinceNoDpad++;

	if (!controlPressed(CONTROL_UP) &&
		!controlPressed(CONTROL_DOWN) &&
		!controlPressed(CONTROL_LEFT) &&
		!controlPressed(CONTROL_RIGHT))
	{
		cyclesSinceNoDpad = 0;
	}

	if (screenshotTakenShowTime > 0)
		screenshotTakenShowTime--;

	if (volumeIndicatorTimeLeft > 0)
		volumeIndicatorTimeLeft--;

	if (controlJustPressed(CONTROL_L) ||
		(controlPressed(CONTROL_BUTTON4) && controlJustPressed(CONTROL_DOWN)))
	{
		volumeIndicatorTimeLeft = VOLUME_INDICATOR_SHOW_TIME;
		mixerVolumeDecrease();
	}

	if (controlJustPressed(CONTROL_R) ||
		(controlPressed(CONTROL_BUTTON4) && controlJustPressed(CONTROL_UP)))
	{
		volumeIndicatorTimeLeft = VOLUME_INDICATOR_SHOW_TIME;
		mixerVolumeIncrease();
	}

	switch (gameMode) {
		case GAME_MODE_SPLASH:
			splashShowTime--;
			if (controlJustPressed(CONTROL_BUTTON1) || controlJustPressed(CONTROL_BUTTON5) || splashShowTime == 0)
			{
				gameMode = GAME_MODE_MENU;
				gameModeSub = GAME_MODE_MENU_MAIN;
			}
			break;
		case GAME_MODE_MENU:
			if (controlPressed(CONTROL_BUTTON4))
				break;
			switch (gameModeSub)
			{
				case GAME_MODE_MENU_MAIN:
					if (controlJustPressed(CONTROL_UP))
					{
						if (menu_index > 0)
							menu_index--;
						else
							menu_index = menu_length - 1;
					}
					if (controlJustPressed(CONTROL_DOWN))
					{
						if (menu_index < menu_length - 1)
							menu_index++;
						else
							menu_index = 0;
					}

					if (controlJustPressed(CONTROL_BUTTON1))
					{
						switch (menu_index) {
							case 0:
								startLevel(configLastLevel);
								break;
							case 1:
								gameModeSub = GAME_MODE_MENU_LEVELLIST;
								break;
							case 2:
								gameModeSub = GAME_MODE_MENU_OPTIONS;
								break;
							case 3:
								gameModeSub = GAME_MODE_MENU_INSTRUCTIONS;
								break;
							case 4:
								gameModeSub = GAME_MODE_MENU_CONTROLS;
								break;
							case 5:
								gameModeSub = GAME_MODE_MENU_CREDITS;
								creditsAnimCounter = 0;
								break;
							case 6:
								gameRunning = false;
								break;
						}
					}
					break;
				case GAME_MODE_MENU_LEVELLIST:
					if (controlRepeat(CONTROL_UP))
					{
						if (levelListSelectedItem > 0)
							levelListSelectedItem--;

						if (levelListSelectedItem - levelListOffset < 0)
							levelListOffset--;
					}
					if (controlRepeat(CONTROL_DOWN))
					{
						if (levelListSelectedItem < numberOfLevels - 1)
							levelListSelectedItem++;

						if (levelListSelectedItem - levelListOffset >= levelListEntriesVisible)
							levelListOffset++;
					}
					if (controlJustPressed(CONTROL_BUTTON1))
					{
						startLevel(levelListSelectedItem + 1);
					}
					if (controlJustPressed(CONTROL_BUTTON2))
					{
						gameModeSub = GAME_MODE_MENU_MAIN;
					}
					break;
				case GAME_MODE_MENU_INSTRUCTIONS:
					if (controlJustPressed(CONTROL_BUTTON2))
					{
						gameModeSub = GAME_MODE_MENU_MAIN;
					}
					break;
				case GAME_MODE_MENU_CONTROLS:
					if (controlJustPressed(CONTROL_BUTTON2))
					{
						gameModeSub = GAME_MODE_MENU_MAIN;
					}
					break;
				case GAME_MODE_MENU_OPTIONS:
					if (controlJustPressed(CONTROL_LEFT) || controlJustPressed(CONTROL_RIGHT))
					{
						curOption = (curOption + 1) % 2;
					}
					if (curOption == 0)
					{
						if (controlJustPressed(CONTROL_UP))
						{
							if (configSound == 0)
								configSound = 2;
							else
								configSound--;
						}
						if (controlJustPressed(CONTROL_DOWN))
						{
							configSound = (configSound + 1) % 3;
						}
					}
					else
					{
						if (controlJustPressed(CONTROL_UP))
						{
							if (configMusic == 0)
								configMusic = 1;
							else
								configMusic--;
						}
						if (controlJustPressed(CONTROL_DOWN))
						{
							configMusic = (configMusic + 1) % 2;
						}
					}

					if (controlJustPressed(CONTROL_BUTTON2) || controlJustPressed(CONTROL_BUTTON1))
					{
						if (configMusic == CONFIG_MUSIC_ON)
							loadMusic();
						else
							unloadMusic();
						gameModeSub = GAME_MODE_MENU_MAIN;
					}
					break;
				case GAME_MODE_MENU_CREDITS:
					creditsAnimCounter++;
					if (controlJustPressed(CONTROL_BUTTON2) || controlJustPressed(CONTROL_BUTTON1) ||
						creditsAnimCounter >= 1590)
					{
						gameModeSub = GAME_MODE_MENU_MAIN;
					}
					break;
			}
			break;
		case GAME_MODE_GAME:
			if (!exitConfirmDialog && controlPressed(CONTROL_BUTTON4))
			{
				if (controlRepeat(CONTROL_LEFT))
				{
					if (cur_level->number > 1)
						startLevel(cur_level->number - 1);
				}
				else if (controlRepeat(CONTROL_RIGHT))
				{
					if (cur_level->number < numberOfLevels)
						startLevel(cur_level->number + 1);
				}
			}

			if (exitConfirmDialog && !controlPressed(CONTROL_BUTTON4))
			{
				if (controlJustPressed(CONTROL_UP))
				{
					if (exitConfirmMenuSelectedItem == 0)
						exitConfirmMenuSelectedItem = exitConfirmMenuLength - 1;
					else
						exitConfirmMenuSelectedItem--;

					if (exitConfirmMenuSelectedItem == 4 && !isLevelSkippable(cur_level->number))
						exitConfirmMenuSelectedItem--;
					if (exitConfirmMenuSelectedItem == 3 && cur_level->number <= 1)
						exitConfirmMenuSelectedItem--;
					if (exitConfirmMenuSelectedItem == 2 && cur_level->number >= numberOfLevels)
						exitConfirmMenuSelectedItem--;
				}
				else if (controlJustPressed(CONTROL_DOWN))
				{
					if (exitConfirmMenuSelectedItem == (exitConfirmMenuLength - 1))
						exitConfirmMenuSelectedItem = 0;
					else
						exitConfirmMenuSelectedItem++;

					if (exitConfirmMenuSelectedItem == 2 && cur_level->number >= numberOfLevels)
						exitConfirmMenuSelectedItem++;
					if (exitConfirmMenuSelectedItem == 3 && cur_level->number <= 1)
						exitConfirmMenuSelectedItem++;
					if (exitConfirmMenuSelectedItem == 4 && !isLevelSkippable(cur_level->number))
						exitConfirmMenuSelectedItem++;
				}
				else if (controlJustPressed(CONTROL_BUTTON1))
				{
					switch (exitConfirmMenuSelectedItem)
					{
						case 0: // Resume
							exitConfirmDialog = false;
							break;
						case 1: // Restart Level
							exitConfirmDialog = false;
							startLevel(cur_level->number);
							break;
						case 2: // Next Level
							if (cur_level->number < numberOfLevels)
							{
								exitConfirmDialog = false;
								startLevel(cur_level->number + 1);
							}
							break;
						case 3: // Previous Level
							if (cur_level->number > 1)
							{
								exitConfirmDialog = false;
								startLevel(cur_level->number - 1);
							}
							break;
						case 4: // Skip Level
							exitConfirmDialog = false;
							skipLevel(cur_level->number);
							break;
						case 5:
							exitConfirmDialog = false;
							gameMode = GAME_MODE_MENU;
							gameModeSub = GAME_MODE_MENU_MAIN;
							cur_level = NULL;
							break;
						case 6:
							gameRunning = false;
							break;
					}
				}
				else if (controlJustPressed(CONTROL_BUTTON2))
				{
					exitConfirmDialog = false;
				}
				else if (controlJustPressed(CONTROL_BUTTON5))
				{
					exitConfirmDialog = false;
				}
			}
			else if (!controlPressed(CONTROL_BUTTON4))
			{
				switch(gameModeSub) {
					case GAME_MODE_GAME_SPLASH:
						if (!controlPressed(CONTROL_BUTTON4) && 
							(controlJustPressed(CONTROL_BUTTON5) ||
							controlJustPressed(CONTROL_BUTTON1) ||
							controlJustPressed(CONTROL_BUTTON2) ||
							controlJustPressed(CONTROL_UP) ||
							controlJustPressed(CONTROL_DOWN) ||
							controlJustPressed(CONTROL_LEFT) ||
							controlJustPressed(CONTROL_RIGHT)))
						{
							gameModeSub = GAME_MODE_GAME_PLAY;
						}
						else
						{
							break;
						}
					case GAME_MODE_GAME_PLAY:
						// X to restart
						if (controlJustPressed(CONTROL_BUTTON3))
						{
							startLevel(cur_level->number);
							break;
						}

						doLogic();
						centerView();

						if ((controlJustPressed(CONTROL_BUTTON2) || controlJustPressed(CONTROL_BUTTON5)) &&
							gameModeSub == GAME_MODE_GAME_PLAY)
						{
							exitConfirmDialog = true;
							exitConfirmMenuSelectedItem = 0;
						}
						break;
					case GAME_MODE_GAME_WON:
						if (controlJustPressed(CONTROL_BUTTON1))
						{
							// When winning certain levels, you win the game
							if (cur_level->number == 144 || cur_level->number == 149)
							{
								gameMode = GAME_MODE_VICTORY;
								initVictoryAnimation();
							}
							else
							{
								startLevel(cur_level->number + 1);
							}
						}
						break;
					case GAME_MODE_GAME_GAMEOVER:
						if (controlJustPressed(CONTROL_BUTTON3) || controlJustPressed(CONTROL_BUTTON1))
							startLevel(cur_level->number);
						break;
					case GAME_MODE_GAME_PASSWORD:
						doPasswordInput();
						break;
				}
			}
			break;
		case GAME_MODE_ERROR:
			if (controlJustPressed(CONTROL_BUTTON5) || 
				controlJustPressed(CONTROL_BUTTON1) || 
				controlJustPressed(CONTROL_BUTTON2))
			{
				gameRunning = false;
			}
			break;
		case GAME_MODE_VICTORY:
			if (victoryFade > 100)
			{
				if (controlJustPressed(CONTROL_BUTTON5) || 
					controlJustPressed(CONTROL_BUTTON1) || 
					controlJustPressed(CONTROL_BUTTON2))
				{
					victoryFade = 100;
				}
			}
			else if (victoryFade == 0)
			{
				gameMode = GAME_MODE_MENU;
				gameModeSub = GAME_MODE_MENU_CREDITS;
				creditsAnimCounter = 0;
			}
			doVictoryAnimation();
			break;
	}
}

void draw()
{
	switch(gameMode) {
		case GAME_MODE_SPLASH:
			drawSplash();
			break;
		case GAME_MODE_MENU:
			switch (gameModeSub)
			{
				case GAME_MODE_MENU_MAIN:
					drawMenu(menu_index);
					break;
				case GAME_MODE_MENU_LEVELLIST:
					drawMenu(menu_index);
					drawLevelList(levelListOffset, levelListSelectedItem);
					break;
				case GAME_MODE_MENU_INSTRUCTIONS:
					drawMenu(menu_index);
					drawInstructions();
					break;
				case GAME_MODE_MENU_CONTROLS:
					drawMenu(menu_index);
					drawControls();
					break;
				case GAME_MODE_MENU_OPTIONS:
					drawMenu(menu_index);
					drawOptions(curOption);
					break;
				case GAME_MODE_MENU_CREDITS:
					drawCredits();
					break;
			}
			break;
		case GAME_MODE_GAME:
			drawGame();
			break;
		case GAME_MODE_ERROR:
			drawError(errorMsg, errorMsg2);
			break;
		case GAME_MODE_VICTORY:
			drawVictoryAnimation();
	}

	if (volumeIndicatorTimeLeft > 0)
		drawVolumeIndicator(mixerVolume, VOLUME_MAX);

	if (screenshotTakenShowTime > 0)
	{
		drawMenuWindow(70, 80, 50, "Screenshot");
		drawTextCenter(108, gameFont, "Screenshot taken!");
	}
	else if (selectButtonCurHoldTime >= (SELECT_BUTTON_HOLD_TIME + 210) && selectButtonCurHoldTime < (SELECT_BUTTON_HOLD_TIME + 330))
	{
		drawMenuWindow(70, 80, 50, "Please stop pushing me");
		drawText(100, 100, gameFont, "You know, you can let\ngo of the button now");
	}

	flipDisplay();
}

int main(int argc, char** argv)
{
	int ref = EXIT_SUCCESS;

	srand(OSTimeGet());

	controlInit();
	if(!initGfx()) {
		controlUnload();
		return ref;
	}

	clearScreen(COLOR_BLACK);
	flipDisplay();

#if defined(SML)
	control_lock(timer_resolution / 4);
#endif

	if (!loadFirstGfx())
	{
		controlUnload();
		termGfx();
		return ref;
	}

	gameTimer = timer_create();

	uint32_t tempTick  = 0;
	uint32_t tempPauseOS = 0;

	// Future .dat selection dialog
	sprintf(chipsDatFileName, "chips.dat");

	drawSplash();
	flipDisplay();

	// Check if patching is needed for chips.dat (spirals corruption)
	checkAndFixSpiralsCorruption();

	if (!loadGame())
		gameMode = GAME_MODE_ERROR;

	if (gameMode != GAME_MODE_ERROR)
	{
		loadData();

		mixerInit(44100, 16, 2, configVolume);
		loadMusic();
		mixerPause(false);
	}

	int sysref;
	while (gameRunning)
	{
		tempTick += timer_delta(gameTimer);
		if(tempTick < gameTickRate) {
			tempPauseOS = (((gameTickRate - tempTick) * OS_TICKS_PER_SEC) / timer_resolution);
			if(tempPauseOS > 0) OSTimeDly(tempPauseOS);
			while(tempTick < gameTickRate)
				tempTick += timer_delta(gameTimer);
		}

		// --- UPDATE ---
		if (gameTicksDoNotCatchUp)
		{
			tempTick = gameTickRate;
			gameTicksDoNotCatchUp = false;
		}

		while(tempTick >= gameTickRate) {
			tempTick -= gameTickRate;

			// Check events
#if defined(SML)
			sysref = sys_judge_event(NULL);
			if (sysref < 0)
			{
				ref = sysref;
				gameRunning = true;
			}
#elif defined(SDL)
			while (SDL_PollEvent(&event))
			{
				if (event.type == SDL_QUIT)
				{
					gameRunning = true;
				}
			}
#endif

			controlUpdate();
			if (controlPressed(CONTROL_BUTTON5) && controlPressed(CONTROL_BUTTON6))
			{
				gameRunning = false;
				break;
			}

			update();
		}
		// --- DRAW ---
		draw();

		mixerUpdate();
	}

	mixerPause(true);
	mixerClose();

	if (dataShouldSave)
	{
		drawExitScreen(EXIT_SAVING);
		flipDisplay();
		saveData();
	}
	
	drawExitScreen(EXIT_EXITING);
	flipDisplay();
	
	timer_delete(gameTimer);

	unloadGame();

	termGfx();
	controlUnload();

	return ref;
}

/*int main(int argc, char** argv)
{
	// INIT
	if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) != 0)
		return 1;

	screen = SDL_SetVideoMode(320, 240, 16, SDL_SWSURFACE);
	if (screen == NULL)
	{
		screen = SDL_SetVideoMode(320, 240, 32, SDL_SWSURFACE);
		if (screen == NULL)
		{
			SDL_Quit();
			return 2;
		}
	}

	bpp = screen->format->BytesPerPixel;

	int currentTime = SDL_GetTicks();
	double accumulator = 0;
	double frameTime = 1000.0/(double)FPS;
	int newTime;
	int deltaTime;

	while (gameRunning)
	{
		newTime = SDL_GetTicks();
		deltaTime = newTime - currentTime;
		currentTime = newTime;

		accumulator += deltaTime;

		while (accumulator >= frameTime)
		{
			accumulator -= frameTime;
			
			// UPDATE
		}
	}

	// LOOP n stuff

	SDL_Quit();
	return 0;
}*/
